Add ability to transform string fields in atom events.

- Add string transformation config in SimpleAtomMatcher which includes
  regex string and the replacement string.
  - CombinationMatcher is not supported.
- Apply transformation for events before atom matching.

Bug: 308006155
Test: statsd_test
Change-Id: Ic76081b2bdac6c3bd1cce639d4468b229efa6a16
diff --git a/lib/libstatsgtestmatchers/include/gtest_matchers.h b/lib/libstatsgtestmatchers/include/gtest_matchers.h
index 084346d..3d9b792 100644
--- a/lib/libstatsgtestmatchers/include/gtest_matchers.h
+++ b/lib/libstatsgtestmatchers/include/gtest_matchers.h
@@ -206,13 +206,30 @@
 EQ_MATCHER(PluggedStateChanged, PROPERTY_EQ(PluggedStateChanged, state));
 TYPE_PRINTER(PluggedStateChanged, PROPERTY_PRINT(state));
 
+EQ_MATCHER(WakelockStateChanged,
+        REPEATED_PROPERTY_MATCHER(WakelockStateChanged, attribution_node, EqAttributionNode),
+        PROPERTY_EQ(WakelockStateChanged, type),
+        PROPERTY_EQ(WakelockStateChanged, tag),
+        PROPERTY_EQ(WakelockStateChanged, state),
+        PROPERTY_EQ(WakelockStateChanged, process_state)
+);
+TYPE_PRINTER(WakelockStateChanged,
+        REPEATED_PROPERTY_PRINT(attribution_node)
+        PROPERTY_PRINT(type)
+        PROPERTY_PRINT(tag)
+        PROPERTY_PRINT(state)
+        PROPERTY_PRINT(process_state)
+);
+
 EQ_MATCHER(Atom,
         PROPERTY_MATCHER(Atom, screen_state_changed, EqScreenStateChanged),
-        PROPERTY_MATCHER(Atom, test_atom_reported, EqTestAtomReported)
+        PROPERTY_MATCHER(Atom, test_atom_reported, EqTestAtomReported),
+        PROPERTY_MATCHER(Atom, wakelock_state_changed, EqWakelockStateChanged)
 );
 TYPE_PRINTER(Atom,
         PROPERTY_PRINT(screen_state_changed)
         PROPERTY_PRINT(test_atom_reported)
+        PROPERTY_PRINT(wakelock_state_changed)
 );
 
 EQ_MATCHER(ShellData,
diff --git a/statsd/Android.bp b/statsd/Android.bp
index d1b4a2e..eea4ee8 100644
--- a/statsd/Android.bp
+++ b/statsd/Android.bp
@@ -116,6 +116,7 @@
         "server_configurable_flags",
         "statsd-aidl-ndk",
         "libsqlite_static_noicu",
+        "libstats_regex",
     ],
     shared_libs: [
         "libbinder_ndk",
@@ -231,7 +232,6 @@
     min_sdk_version: "30",
 }
 
-
 cc_defaults {
     name: "statsd_test_defaults",
     defaults: ["statsd_defaults"],
@@ -367,9 +367,10 @@
         "tests/e2e/MetricConditionLink_e2e_test.cpp",
         "tests/e2e/PartialBucket_e2e_test.cpp",
         "tests/e2e/RestrictedConfig_e2e_test.cpp",
+        "tests/e2e/RestrictedEventMetric_e2e_test.cpp",
+        "tests/e2e/StringReplace_e2e_test.cpp",
         "tests/e2e/ValueMetric_pull_e2e_test.cpp",
         "tests/e2e/WakelockDuration_e2e_test.cpp",
-        "tests/e2e/RestrictedEventMetric_e2e_test.cpp",
         "tests/external/puller_util_test.cpp",
         "tests/external/StatsCallbackPuller_test.cpp",
         "tests/external/StatsPuller_test.cpp",
@@ -452,6 +453,19 @@
     ],
 }
 
+// Build regex support in a separate library to catch std::regex_error exception.
+cc_library_static {
+    name: "libstats_regex",
+    srcs: ["lib/stats_regex.cpp"],
+    cppflags: ["-fexceptions"],
+    static_libs: ["libbase"],
+    min_sdk_version: "30",
+    apex_available: [
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
+}
+
 // ====  java proto device library (for test only)  ==============================
 java_library {
     name: "statsdprotolite",
diff --git a/statsd/lib/stats_regex.cpp b/statsd/lib/stats_regex.cpp
new file mode 100644
index 0000000..b041f66
--- /dev/null
+++ b/statsd/lib/stats_regex.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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 "stats_regex.h"
+
+#include <log/log.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::regex;
+using std::regex_error;
+using std::regex_replace;
+using std::string;
+using std::unique_ptr;
+
+unique_ptr<regex> createRegex(const string& pattern) {
+    try {
+        return std::make_unique<regex>(pattern);
+    } catch (regex_error& e) {
+        ALOGE("regex_error: %s, pattern: %s", e.what(), pattern.c_str());
+        return nullptr;
+    }
+}
+
+string regexReplace(const string& input, const regex& re, const string& format) {
+    try {
+        return regex_replace(input, re, format);
+    } catch (regex_error& e) {
+        ALOGE("regex_error: %s, input: %s, format: %s", e.what(), input.c_str(), format.c_str());
+        return input;
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/lib/stats_regex.h b/statsd/lib/stats_regex.h
new file mode 100644
index 0000000..99e76b2
--- /dev/null
+++ b/statsd/lib/stats_regex.h
@@ -0,0 +1,31 @@
+/*
+ * 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 <regex>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+std::unique_ptr<std::regex> createRegex(const std::string& pattern);
+
+std::string regexReplace(const std::string& input, const std::regex& re, const std::string& format);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/StatsLogProcessor.h b/statsd/src/StatsLogProcessor.h
index 1449911..d3e5d82 100644
--- a/statsd/src/StatsLogProcessor.h
+++ b/statsd/src/StatsLogProcessor.h
@@ -488,6 +488,10 @@
     FRIEND_TEST(KllMetricE2eTest, TestInitWithKllFieldPositionALL);
 
     FRIEND_TEST(StatsServiceStatsdInitTest, StatsServiceStatsdInitTest);
+
+    FRIEND_TEST(StringReplaceE2eTest, TestPulledDimension);
+    FRIEND_TEST(StringReplaceE2eTest, TestPulledWhat);
+    FRIEND_TEST(StringReplaceE2eTest, TestMultipleMatchersForAtom);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/guardrail/stats_log_enums.proto b/statsd/src/guardrail/stats_log_enums.proto
index 5fcdfda..d47bc80 100644
--- a/statsd/src/guardrail/stats_log_enums.proto
+++ b/statsd/src/guardrail/stats_log_enums.proto
@@ -143,6 +143,11 @@
     INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED = 85;
     INVALID_CONFIG_REASON_METRIC_INCORRECT_SAMPLING_PERCENTAGE = 86;
     INVALID_CONFIG_REASON_GAUGE_METRIC_PULLED_WITH_SAMPLING = 87;
+    INVALID_CONFIG_REASON_MATCHER_NO_VALUE_MATCHER_NOR_STRING_REPLACER = 88;
+    INVALID_CONFIG_REASON_MATCHER_VALUE_MATCHER_WITH_POSITION_ALL = 89;
+    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;
 };
 
 enum InvalidQueryReason {
diff --git a/statsd/src/matchers/AtomMatchingTracker.h b/statsd/src/matchers/AtomMatchingTracker.h
index 308f7e1..49c35b2 100644
--- a/statsd/src/matchers/AtomMatchingTracker.h
+++ b/statsd/src/matchers/AtomMatchingTracker.h
@@ -32,6 +32,11 @@
 namespace os {
 namespace statsd {
 
+struct MatcherInitResult {
+    optional<InvalidConfigReason> invalidConfigReason;
+    bool hasStringTransformation;
+};
+
 class AtomMatchingTracker : public virtual RefBase {
 public:
     AtomMatchingTracker(const int64_t id, const uint64_t protoHash)
@@ -49,7 +54,7 @@
     //                          CombinationAtomMatchingTrackers using DFS.
     // stack: a bit map to record which matcher has been visited on the stack. This is for detecting
     //        circle dependency.
-    virtual optional<InvalidConfigReason> init(
+    virtual MatcherInitResult init(
             int matcherIndex, const std::vector<AtomMatcher>& allAtomMatchers,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
             const std::unordered_map<int64_t, int>& matcherMap, std::vector<uint8_t>& stack) = 0;
@@ -70,9 +75,11 @@
     // matcherResults: The cached results for all matchers for this event. Parent matchers can
     //                 directly access the children's matching results if they have been evaluated.
     //                 Otherwise, call children matchers' onLogEvent.
+    // matcherTransformations: the cached transformations for all matchers for this event.
     virtual void onLogEvent(const LogEvent& event, int matcherIndex,
                             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-                            std::vector<MatchingState>& matcherResults) = 0;
+                            std::vector<MatchingState>& matcherResults,
+                            std::vector<std::shared_ptr<LogEvent>>& matcherTransformations) = 0;
 
     // Get the tagIds that this matcher cares about. The combined collection is stored
     // in MetricMananger, so that we can pass any LogEvents that are not interest of us. It uses
diff --git a/statsd/src/matchers/CombinationAtomMatchingTracker.cpp b/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
index 68811cf..bc75eef 100644
--- a/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
+++ b/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
@@ -25,6 +25,7 @@
 namespace statsd {
 
 using std::set;
+using std::shared_ptr;
 using std::unordered_map;
 using std::vector;
 
@@ -36,12 +37,16 @@
 CombinationAtomMatchingTracker::~CombinationAtomMatchingTracker() {
 }
 
-optional<InvalidConfigReason> CombinationAtomMatchingTracker::init(
+MatcherInitResult CombinationAtomMatchingTracker::init(
         int matcherIndex, const vector<AtomMatcher>& allAtomMatchers,
         const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const unordered_map<int64_t, int>& matcherMap, vector<uint8_t>& stack) {
+    MatcherInitResult result{nullopt /* invalidConfigReason */,
+                             false /* hasStringTransformation */};
     if (mInitialized) {
-        return nullopt;
+        // CombinationMatchers do not support string transformations so if mInitialized = true,
+        // we know that there is no string transformation and we do not need to check for it again.
+        return result;
     }
 
     // mark this node as visited in the recursion stack.
@@ -51,26 +56,27 @@
 
     // LogicalOperation is missing in the config
     if (!matcher.has_operation()) {
-        return createInvalidConfigReasonWithMatcher(INVALID_CONFIG_REASON_MATCHER_NO_OPERATION,
-                                                    mId);
+        result.invalidConfigReason = createInvalidConfigReasonWithMatcher(
+                INVALID_CONFIG_REASON_MATCHER_NO_OPERATION, mId);
+        return result;
     }
 
     mLogicalOperation = matcher.operation();
 
     if (mLogicalOperation == LogicalOperation::NOT && matcher.matcher_size() != 1) {
-        return createInvalidConfigReasonWithMatcher(
+        result.invalidConfigReason = createInvalidConfigReasonWithMatcher(
                 INVALID_CONFIG_REASON_MATCHER_NOT_OPERATION_IS_NOT_UNARY, mId);
+        return result;
     }
 
     for (const auto& child : matcher.matcher()) {
         auto pair = matcherMap.find(child);
         if (pair == matcherMap.end()) {
             ALOGW("Matcher %lld not found in the config", (long long)child);
-            optional<InvalidConfigReason> invalidConfigReason =
-                    createInvalidConfigReasonWithMatcher(
-                            INVALID_CONFIG_REASON_MATCHER_CHILD_NOT_FOUND, mId);
-            invalidConfigReason->matcherIds.push_back(child);
-            return invalidConfigReason;
+            result.invalidConfigReason = createInvalidConfigReasonWithMatcher(
+                    INVALID_CONFIG_REASON_MATCHER_CHILD_NOT_FOUND, mId);
+            result.invalidConfigReason->matcherIds.push_back(child);
+            return result;
         }
 
         int childIndex = pair->second;
@@ -78,18 +84,27 @@
         // if the child is a visited node in the recursion -> circle detected.
         if (stack[childIndex]) {
             ALOGE("Circle detected in matcher config");
-            optional<InvalidConfigReason> invalidConfigReason =
+            result.invalidConfigReason =
                     createInvalidConfigReasonWithMatcher(INVALID_CONFIG_REASON_MATCHER_CYCLE, mId);
-            invalidConfigReason->matcherIds.push_back(child);
-            return invalidConfigReason;
+            result.invalidConfigReason->matcherIds.push_back(child);
+            return result;
         }
-        optional<InvalidConfigReason> invalidConfigReason =
+        auto [invalidConfigReason, hasStringTransformation] =
                 allAtomMatchingTrackers[childIndex]->init(
                         childIndex, allAtomMatchers, allAtomMatchingTrackers, matcherMap, stack);
+        if (hasStringTransformation) {
+            ALOGE("String transformation detected in CombinationMatcher");
+            result.invalidConfigReason = createInvalidConfigReasonWithMatcher(
+                    INVALID_CONFIG_REASON_MATCHER_COMBINATION_WITH_STRING_REPLACE, mId);
+            result.hasStringTransformation = true;
+            return result;
+        }
+
         if (invalidConfigReason.has_value()) {
             ALOGW("child matcher init failed %lld", (long long)child);
             invalidConfigReason->matcherIds.push_back(mId);
-            return invalidConfigReason;
+            result.invalidConfigReason = invalidConfigReason;
+            return result;
         }
 
         mChildren.push_back(childIndex);
@@ -101,7 +116,7 @@
     mInitialized = true;
     // unmark this node in the recursion stack.
     stack[matcherIndex] = false;
-    return nullopt;
+    return result;
 }
 
 optional<InvalidConfigReason> CombinationAtomMatchingTracker::onConfigUpdated(
@@ -126,7 +141,8 @@
 void CombinationAtomMatchingTracker::onLogEvent(
         const LogEvent& event, int matcherIndex,
         const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-        vector<MatchingState>& matcherResults) {
+        vector<MatchingState>& matcherResults,
+        vector<shared_ptr<LogEvent>>& matcherTransformations) {
     // this event has been processed.
     if (matcherResults[matcherIndex] != MatchingState::kNotComputed) {
         return;
@@ -141,7 +157,8 @@
     for (const int childIndex : mChildren) {
         if (matcherResults[childIndex] == MatchingState::kNotComputed) {
             const sp<AtomMatchingTracker>& child = allAtomMatchingTrackers[childIndex];
-            child->onLogEvent(event, childIndex, allAtomMatchingTrackers, matcherResults);
+            child->onLogEvent(event, childIndex, allAtomMatchingTrackers, matcherResults,
+                              matcherTransformations);
         }
     }
 
diff --git a/statsd/src/matchers/CombinationAtomMatchingTracker.h b/statsd/src/matchers/CombinationAtomMatchingTracker.h
index 1a6146c..9e06533 100644
--- a/statsd/src/matchers/CombinationAtomMatchingTracker.h
+++ b/statsd/src/matchers/CombinationAtomMatchingTracker.h
@@ -31,10 +31,10 @@
 public:
     CombinationAtomMatchingTracker(const int64_t id, const uint64_t protoHash);
 
-    optional<InvalidConfigReason> init(
-            int matcherIndex, const std::vector<AtomMatcher>& allAtomMatchers,
-            const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-            const std::unordered_map<int64_t, int>& matcherMap, std::vector<uint8_t>& stack);
+    MatcherInitResult init(int matcherIndex, const std::vector<AtomMatcher>& allAtomMatchers,
+                           const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                           const std::unordered_map<int64_t, int>& matcherMap,
+                           std::vector<uint8_t>& stack);
 
     optional<InvalidConfigReason> onConfigUpdated(
             const AtomMatcher& matcher,
@@ -44,7 +44,8 @@
 
     void onLogEvent(const LogEvent& event, int matcherIndex,
                     const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-                    std::vector<MatchingState>& matcherResults) override;
+                    std::vector<MatchingState>& matcherResults,
+                    std::vector<std::shared_ptr<LogEvent>>& matcherTransformations) override;
 
 private:
     LogicalOperation mLogicalOperation;
diff --git a/statsd/src/matchers/EventMatcherWizard.cpp b/statsd/src/matchers/EventMatcherWizard.cpp
index 18a9074..1dbe5e0 100644
--- a/statsd/src/matchers/EventMatcherWizard.cpp
+++ b/statsd/src/matchers/EventMatcherWizard.cpp
@@ -21,14 +21,16 @@
 
 using std::vector;
 
-MatchingState EventMatcherWizard::matchLogEvent(const LogEvent& event, int matcherIndex) {
+MatchLogEventResult EventMatcherWizard::matchLogEvent(const LogEvent& event, int matcherIndex) {
     if (matcherIndex < 0 || matcherIndex >= (int)mAllEventMatchers.size()) {
-        return MatchingState::kNotComputed;
+        return {MatchingState::kNotComputed, nullptr};
     }
     std::fill(mMatcherCache.begin(), mMatcherCache.end(), MatchingState::kNotComputed);
+    std::fill(mMatcherTransformations.begin(), mMatcherTransformations.end(), nullptr);
     mAllEventMatchers[matcherIndex]->onLogEvent(event, matcherIndex, mAllEventMatchers,
-                                                mMatcherCache);
-    return mMatcherCache[matcherIndex];
+                                                mMatcherCache, mMatcherTransformations);
+
+    return {mMatcherCache[matcherIndex], mMatcherTransformations[matcherIndex]};
 }
 
 }  // namespace statsd
diff --git a/statsd/src/matchers/EventMatcherWizard.h b/statsd/src/matchers/EventMatcherWizard.h
index c4ad150..b2ef311 100644
--- a/statsd/src/matchers/EventMatcherWizard.h
+++ b/statsd/src/matchers/EventMatcherWizard.h
@@ -22,20 +22,27 @@
 namespace os {
 namespace statsd {
 
+struct MatchLogEventResult {
+    MatchingState matchingState;
+    std::shared_ptr<LogEvent> transformedEvent;
+};
+
 class EventMatcherWizard : public virtual RefBase {
 public:
     EventMatcherWizard(){};  // for testing
     EventMatcherWizard(const std::vector<sp<AtomMatchingTracker>>& eventTrackers)
         : mAllEventMatchers(eventTrackers),
-          mMatcherCache(eventTrackers.size(), MatchingState::kNotComputed){};
+          mMatcherCache(eventTrackers.size(), MatchingState::kNotComputed),
+          mMatcherTransformations(eventTrackers.size(), nullptr){};
 
     virtual ~EventMatcherWizard(){};
 
-    MatchingState matchLogEvent(const LogEvent& event, int matcherIndex);
+    MatchLogEventResult matchLogEvent(const LogEvent& event, int matcherIndex);
 
 private:
     std::vector<sp<AtomMatchingTracker>> mAllEventMatchers;
     std::vector<MatchingState> mMatcherCache;
+    std::vector<std::shared_ptr<LogEvent>> mMatcherTransformations;
 };
 
 }  // namespace statsd
diff --git a/statsd/src/matchers/SimpleAtomMatchingTracker.cpp b/statsd/src/matchers/SimpleAtomMatchingTracker.cpp
index 81d0710..db45560 100644
--- a/statsd/src/matchers/SimpleAtomMatchingTracker.cpp
+++ b/statsd/src/matchers/SimpleAtomMatchingTracker.cpp
@@ -23,6 +23,7 @@
 namespace os {
 namespace statsd {
 
+using std::shared_ptr;
 using std::unordered_map;
 using std::vector;
 
@@ -41,16 +42,27 @@
 SimpleAtomMatchingTracker::~SimpleAtomMatchingTracker() {
 }
 
-optional<InvalidConfigReason> SimpleAtomMatchingTracker::init(
+MatcherInitResult SimpleAtomMatchingTracker::init(
         int matcherIndex, const vector<AtomMatcher>& allAtomMatchers,
         const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const unordered_map<int64_t, int>& matcherMap, vector<uint8_t>& stack) {
+    MatcherInitResult result{nullopt /* invalidConfigReason */,
+                             false /* hasStringTransformation */};
     // no need to do anything.
     if (!mInitialized) {
-        return createInvalidConfigReasonWithMatcher(
+        result.invalidConfigReason = createInvalidConfigReasonWithMatcher(
                 INVALID_CONFIG_REASON_MATCHER_TRACKER_NOT_INITIALIZED, mId);
+        return result;
     }
-    return nullopt;
+
+    for (const FieldValueMatcher& fvm : mMatcher.field_value_matcher()) {
+        if (fvm.has_replace_string()) {
+            result.hasStringTransformation = true;
+            break;
+        }
+    }
+
+    return result;
 }
 
 optional<InvalidConfigReason> SimpleAtomMatchingTracker::onConfigUpdated(
@@ -66,7 +78,8 @@
 void SimpleAtomMatchingTracker::onLogEvent(
         const LogEvent& event, int matcherIndex,
         const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-        vector<MatchingState>& matcherResults) {
+        vector<MatchingState>& matcherResults,
+        vector<shared_ptr<LogEvent>>& matcherTransformations) {
     if (matcherResults[matcherIndex] != MatchingState::kNotComputed) {
         VLOG("Matcher %lld already evaluated ", (long long)mId);
         return;
@@ -77,9 +90,13 @@
         return;
     }
 
-    bool matched = matchesSimple(mUidMap, mMatcher, event);
+    auto [matched, transformedEvent] = matchesSimple(mUidMap, mMatcher, event);
     matcherResults[matcherIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
     VLOG("Stats SimpleAtomMatcher %lld matched? %d", (long long)mId, matched);
+
+    if (matched && transformedEvent != nullptr) {
+        matcherTransformations[matcherIndex] = std::move(transformedEvent);
+    }
 }
 
 }  // namespace statsd
diff --git a/statsd/src/matchers/SimpleAtomMatchingTracker.h b/statsd/src/matchers/SimpleAtomMatchingTracker.h
index 0cfb248..641e9d8 100644
--- a/statsd/src/matchers/SimpleAtomMatchingTracker.h
+++ b/statsd/src/matchers/SimpleAtomMatchingTracker.h
@@ -35,11 +35,10 @@
 
     ~SimpleAtomMatchingTracker();
 
-    optional<InvalidConfigReason> init(
-            int matcherIndex, const std::vector<AtomMatcher>& allAtomMatchers,
-            const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-            const std::unordered_map<int64_t, int>& matcherMap,
-            std::vector<uint8_t>& stack) override;
+    MatcherInitResult init(int matcherIndex, const std::vector<AtomMatcher>& allAtomMatchers,
+                           const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+                           const std::unordered_map<int64_t, int>& matcherMap,
+                           std::vector<uint8_t>& stack) override;
 
     optional<InvalidConfigReason> onConfigUpdated(
             const AtomMatcher& matcher,
@@ -47,7 +46,8 @@
 
     void onLogEvent(const LogEvent& event, int matcherIndex,
                     const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-                    std::vector<MatchingState>& matcherResults) override;
+                    std::vector<MatchingState>& matcherResults,
+                    std::vector<std::shared_ptr<LogEvent>>& matcherTransformations) override;
 
 private:
     const SimpleAtomMatcher mMatcher;
diff --git a/statsd/src/matchers/matcher_util.cpp b/statsd/src/matchers/matcher_util.cpp
index 7b5bcb0..347e484 100644
--- a/statsd/src/matchers/matcher_util.cpp
+++ b/statsd/src/matchers/matcher_util.cpp
@@ -20,12 +20,15 @@
 
 #include <fnmatch.h>
 
+#include "lib/stats_regex.h"
 #include "matchers/AtomMatchingTracker.h"
 #include "src/statsd_config.pb.h"
 #include "stats_util.h"
 
+using std::regex;
 using std::set;
 using std::string;
+using std::unique_ptr;
 using std::vector;
 
 namespace android {
@@ -127,6 +130,40 @@
     return false;
 }
 
+static unique_ptr<LogEvent> getTransformedEvent(const FieldValueMatcher& matcher,
+                                                const LogEvent& event, int start, int end) {
+    if (!matcher.has_replace_string()) {
+        return nullptr;
+    }
+
+    unique_ptr<regex> re = createRegex(matcher.replace_string().regex());
+    if (re == nullptr) {
+        return nullptr;
+    }
+
+    const string& replacement = matcher.replace_string().replacement();
+    unique_ptr<LogEvent> transformedEvent = nullptr;
+    for (int i = start; i < end; i++) {
+        const LogEvent& eventRef = transformedEvent == nullptr ? event : *transformedEvent;
+        const FieldValue& fieldValue = eventRef.getValues()[i];
+        if (fieldValue.mValue.getType() != STRING) {
+            continue;
+        }
+        const string transformedString =
+                regexReplace(fieldValue.mValue.str_value, *re, replacement);
+        if (transformedString == fieldValue.mValue.str_value) {
+            continue;
+        }
+
+        // String transformation occurred, update the FieldValue in transformedEvent.
+        if (transformedEvent == nullptr) {
+            transformedEvent = std::make_unique<LogEvent>(event);
+        }
+        (*transformedEvent->getMutableValues())[i].mValue.str_value = transformedString;
+    }
+    return transformedEvent;
+}
+
 static pair<int, int> getStartEndAtDepth(int targetField, int start, int end, int depth,
                                          const vector<FieldValue>& values) {
     // Filter by entry field first
@@ -201,7 +238,24 @@
                 ranges.push_back(std::make_pair(start, end));
                 break;
             }
+            case Position::ALL:
+                // ALL is only supported for string transformation. If a value_matcher other than
+                // matches_tuple is present, the matcher is invalid. This is enforced when
+                // the AtomMatchingTracker is initialized.
+
+                // fallthrough
             case Position::ANY: {
+                // For string transformation, this case is treated the same as Position:ALL.
+                // Given a matcher on attribution_node[ANY].tag with a matches_tuple containing a
+                // child FieldValueMatcher with eq_string: "foo" and regex_replace: "[\d]+$" --> "",
+                // an event with attribution tags: ["bar123", "foo12", "abc230"] will transform to
+                // have attribution tags ["bar", "foo", "abc"] and will be a successful match.
+
+                // Note that if value_matcher is matches_tuple, there should be no string
+                // transformation on this matcher. However, child FieldValueMatchers in
+                // matches_tuple can have string transformations. This is enforced when
+                // AtomMatchingTracker is initialized.
+
                 if (matcher.value_matcher_case() == FieldValueMatcher::kMatchesTuple) {
                     // For ANY with matches_tuple, if all the children matchers match in any of the
                     // sub trees, it's a match.
@@ -220,9 +274,6 @@
                 ranges.push_back(std::make_pair(start, end));
                 break;
             }
-            case Position::ALL:
-                ALOGE("Not supported: field matcher with ALL position.");
-                break;
             case Position::POSITION_UNKNOWN:
                 break;
         }
@@ -234,43 +285,56 @@
     return ranges;
 }
 
-static bool matchesSimple(const sp<UidMap>& uidMap, const FieldValueMatcher& matcher,
-                          const vector<FieldValue>& values, int start, int end, int depth) {
+static MatchResult matchesSimple(const sp<UidMap>& uidMap, const FieldValueMatcher& matcher,
+                                 const LogEvent& event, int start, int end, int depth) {
     if (depth > 2) {
-        ALOGE("Depth > 3 not supported");
-        return false;
+        ALOGE("Depth >= 3 not supported");
+        return {false, nullptr};
     }
 
     if (start >= end) {
-        return false;
+        return {false, nullptr};
     }
 
-    const vector<pair<int, int>> ranges = computeRanges(matcher, values, start, end, depth);
+    const vector<pair<int, int>> ranges =
+            computeRanges(matcher, event.getValues(), start, end, depth);
 
     if (ranges.empty()) {
         // No such field found.
-        return false;
+        return {false, nullptr};
     }
 
     // ranges should have exactly one start/end pair at this point unless position is ANY and
     // value_matcher is matches_tuple.
     std::tie(start, end) = ranges[0];
 
+    unique_ptr<LogEvent> transformedEvent = getTransformedEvent(matcher, event, start, end);
+
+    const vector<FieldValue>& values =
+            transformedEvent == nullptr ? event.getValues() : transformedEvent->getValues();
+
     switch (matcher.value_matcher_case()) {
         case FieldValueMatcher::kMatchesTuple: {
             ++depth;
             // If any range matches all matchers, good.
+            bool matchResult = false;
             for (const auto& [rangeStart, rangeEnd] : ranges) {
                 bool matched = true;
                 for (const auto& subMatcher : matcher.matches_tuple().field_value_matcher()) {
-                    if (!matchesSimple(uidMap, subMatcher, values, rangeStart, rangeEnd, depth)) {
+                    const LogEvent& eventRef =
+                            transformedEvent == nullptr ? event : *transformedEvent;
+                    auto [hasMatched, newTransformedEvent] = matchesSimple(
+                            uidMap, subMatcher, eventRef, rangeStart, rangeEnd, depth);
+                    if (newTransformedEvent != nullptr) {
+                        transformedEvent = std::move(newTransformedEvent);
+                    }
+                    if (!hasMatched) {
                         matched = false;
-                        break;
                     }
                 }
-                if (matched) return true;
+                matchResult = matchResult || matched;
             }
-            return false;
+            return {matchResult, std::move(transformedEvent)};
         }
         // Finally, we get to the point of real value matching.
         // If the field matcher ends with ANY, then we have [start, end) range > 1.
@@ -281,18 +345,18 @@
                      (values[i].mValue.int_value != 0) == matcher.eq_bool()) ||
                     (values[i].mValue.getType() == LONG &&
                      (values[i].mValue.long_value != 0) == matcher.eq_bool())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kEqString: {
             for (int i = start; i < end; i++) {
                 if (tryMatchString(uidMap, values[i], matcher.eq_string())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kNeqAnyString: {
             const auto& str_list = matcher.neq_any_string();
@@ -305,40 +369,40 @@
                     }
                 }
                 if (notEqAll) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kEqAnyString: {
             const auto& str_list = matcher.eq_any_string();
             for (int i = start; i < end; i++) {
                 for (const auto& str : str_list.str_value()) {
                     if (tryMatchString(uidMap, values[i], str)) {
-                        return true;
+                        return {true, std::move(transformedEvent)};
                     }
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kEqWildcardString: {
             for (int i = start; i < end; i++) {
                 if (tryMatchWildcardString(uidMap, values[i], matcher.eq_wildcard_string())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kEqAnyWildcardString: {
             const auto& str_list = matcher.eq_any_wildcard_string();
             for (int i = start; i < end; i++) {
                 for (const auto& str : str_list.str_value()) {
                     if (tryMatchWildcardString(uidMap, values[i], str)) {
-                        return true;
+                        return {true, std::move(transformedEvent)};
                     }
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kNeqAnyWildcardString: {
             const auto& str_list = matcher.neq_any_wildcard_string();
@@ -351,24 +415,24 @@
                     }
                 }
                 if (notEqAll) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kEqInt: {
             for (int i = start; i < end; i++) {
                 if (values[i].mValue.getType() == INT &&
                     (matcher.eq_int() == values[i].mValue.int_value)) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
                 // eq_int covers both int and long.
                 if (values[i].mValue.getType() == LONG &&
                     (matcher.eq_int() == values[i].mValue.long_value)) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kEqAnyInt: {
             const auto& int_list = matcher.eq_any_int();
@@ -376,16 +440,16 @@
                 for (const int int_value : int_list.int_value()) {
                     if (values[i].mValue.getType() == INT &&
                         (int_value == values[i].mValue.int_value)) {
-                        return true;
+                        return {true, std::move(transformedEvent)};
                     }
                     // eq_any_int covers both int and long.
                     if (values[i].mValue.getType() == LONG &&
                         (int_value == values[i].mValue.long_value)) {
-                        return true;
+                        return {true, std::move(transformedEvent)};
                     }
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kNeqAnyInt: {
             const auto& int_list = matcher.neq_any_int();
@@ -405,102 +469,113 @@
                     }
                 }
                 if (notEqAll) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kLtInt: {
             for (int i = start; i < end; i++) {
                 if (values[i].mValue.getType() == INT &&
                     (values[i].mValue.int_value < matcher.lt_int())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
                 // lt_int covers both int and long.
                 if (values[i].mValue.getType() == LONG &&
                     (values[i].mValue.long_value < matcher.lt_int())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kGtInt: {
             for (int i = start; i < end; i++) {
                 if (values[i].mValue.getType() == INT &&
                     (values[i].mValue.int_value > matcher.gt_int())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
                 // gt_int covers both int and long.
                 if (values[i].mValue.getType() == LONG &&
                     (values[i].mValue.long_value > matcher.gt_int())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kLtFloat: {
             for (int i = start; i < end; i++) {
                 if (values[i].mValue.getType() == FLOAT &&
                     (values[i].mValue.float_value < matcher.lt_float())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kGtFloat: {
             for (int i = start; i < end; i++) {
                 if (values[i].mValue.getType() == FLOAT &&
                     (values[i].mValue.float_value > matcher.gt_float())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kLteInt: {
             for (int i = start; i < end; i++) {
                 if (values[i].mValue.getType() == INT &&
                     (values[i].mValue.int_value <= matcher.lte_int())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
                 // lte_int covers both int and long.
                 if (values[i].mValue.getType() == LONG &&
                     (values[i].mValue.long_value <= matcher.lte_int())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         case FieldValueMatcher::ValueMatcherCase::kGteInt: {
             for (int i = start; i < end; i++) {
                 if (values[i].mValue.getType() == INT &&
                     (values[i].mValue.int_value >= matcher.gte_int())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
                 // gte_int covers both int and long.
                 if (values[i].mValue.getType() == LONG &&
                     (values[i].mValue.long_value >= matcher.gte_int())) {
-                    return true;
+                    return {true, std::move(transformedEvent)};
                 }
             }
-            return false;
+            return {false, std::move(transformedEvent)};
         }
         default:
-            return false;
+            // This only happens if the matcher has a string transformation and no value_matcher. So
+            // the default match result is true. If there is no string transformation either then
+            // this matcher is invalid, which is enforced when the AtomMatchingTracker is
+            // initialized.
+            return {true, std::move(transformedEvent)};
     }
 }
 
-bool matchesSimple(const sp<UidMap>& uidMap, const SimpleAtomMatcher& simpleMatcher,
-                   const LogEvent& event) {
+MatchResult matchesSimple(const sp<UidMap>& uidMap, const SimpleAtomMatcher& simpleMatcher,
+                          const LogEvent& event) {
     if (event.GetTagId() != simpleMatcher.atom_id()) {
-        return false;
+        return {false, nullptr};
     }
 
+    unique_ptr<LogEvent> transformedEvent = nullptr;
     for (const auto& matcher : simpleMatcher.field_value_matcher()) {
-        if (!matchesSimple(uidMap, matcher, event.getValues(), 0, event.getValues().size(), 0)) {
-            return false;
+        const LogEvent& inputEvent = transformedEvent == nullptr ? event : *transformedEvent;
+        auto [hasMatched, newTransformedEvent] =
+                matchesSimple(uidMap, matcher, inputEvent, 0, inputEvent.getValues().size(), 0);
+        if (newTransformedEvent != nullptr) {
+            transformedEvent = std::move(newTransformedEvent);
+        }
+        if (!hasMatched) {
+            return {false, std::move(transformedEvent)};
         }
     }
-    return true;
+    return {true, std::move(transformedEvent)};
 }
 
 }  // namespace statsd
diff --git a/statsd/src/matchers/matcher_util.h b/statsd/src/matchers/matcher_util.h
index 597b74f..5fb7c42 100644
--- a/statsd/src/matchers/matcher_util.h
+++ b/statsd/src/matchers/matcher_util.h
@@ -33,11 +33,16 @@
     kMatched = 1,
 };
 
+struct MatchResult {
+    bool matched;
+    std::unique_ptr<LogEvent> transformedEvent;
+};
+
 bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation,
                       const std::vector<MatchingState>& matcherResults);
 
-bool matchesSimple(const sp<UidMap>& uidMap, const SimpleAtomMatcher& simpleMatcher,
-                   const LogEvent& wrapper);
+MatchResult matchesSimple(const sp<UidMap>& uidMap, const SimpleAtomMatcher& simpleMatcher,
+                          const LogEvent& wrapper);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/statsd/src/metrics/GaugeMetricProducer.cpp b/statsd/src/metrics/GaugeMetricProducer.cpp
index cab858b..e9f9477 100644
--- a/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -410,9 +410,10 @@
         return;
     }
     for (const auto& data : allData) {
-        if (mEventMatcherWizard->matchLogEvent(*data, mWhatMatcherIndex) ==
-            MatchingState::kMatched) {
-            LogEvent localCopy = *data;
+        const auto [matchResult, transformedEvent] =
+                mEventMatcherWizard->matchLogEvent(*data, mWhatMatcherIndex);
+        if (matchResult == MatchingState::kMatched) {
+            LogEvent localCopy = transformedEvent == nullptr ? *data : *transformedEvent;
             localCopy.setElapsedTimestampNs(timestampNs);
             onMatchedLogEventLocked(mWhatMatcherIndex, localCopy);
         }
@@ -502,9 +503,11 @@
         return;
     }
     for (const auto& data : allData) {
-        if (mEventMatcherWizard->matchLogEvent(
-                *data, mWhatMatcherIndex) == MatchingState::kMatched) {
-            onMatchedLogEventLocked(mWhatMatcherIndex, *data);
+        const auto [matchResult, transformedEvent] =
+                mEventMatcherWizard->matchLogEvent(*data, mWhatMatcherIndex);
+        if (matchResult == MatchingState::kMatched) {
+            onMatchedLogEventLocked(mWhatMatcherIndex,
+                                    transformedEvent == nullptr ? *data : *transformedEvent);
         }
     }
 }
diff --git a/statsd/src/metrics/MetricsManager.cpp b/statsd/src/metrics/MetricsManager.cpp
index e7e3561..8826848 100644
--- a/statsd/src/metrics/MetricsManager.cpp
+++ b/statsd/src/metrics/MetricsManager.cpp
@@ -44,6 +44,7 @@
 
 using std::set;
 using std::string;
+using std::unique_ptr;
 using std::vector;
 
 namespace android {
@@ -616,10 +617,12 @@
 
     vector<MatchingState> matcherCache(mAllAtomMatchingTrackers.size(),
                                        MatchingState::kNotComputed);
+    vector<shared_ptr<LogEvent>> matcherTransformations(matcherCache.size(), nullptr);
 
     for (const auto& matcherIndex : matchersIt->second) {
         mAllAtomMatchingTrackers[matcherIndex]->onLogEvent(event, matcherIndex,
-                                                           mAllAtomMatchingTrackers, matcherCache);
+                                                           mAllAtomMatchingTrackers, matcherCache,
+                                                           matcherTransformations);
     }
 
     // Set of metrics that received an activation cancellation.
@@ -661,12 +664,15 @@
 
     // A bitmap to see which ConditionTracker needs to be re-evaluated.
     vector<uint8_t> conditionToBeEvaluated(mAllConditionTrackers.size(), false);
+    vector<shared_ptr<LogEvent>> conditionToTransformedLogEvents(mAllConditionTrackers.size(),
+                                                                 nullptr);
 
-    for (const auto& pair : mTrackerToConditionMap) {
-        if (matcherCache[pair.first] == MatchingState::kMatched) {
-            const auto& conditionList = pair.second;
+    for (const auto& [matcherIndex, conditionList] : mTrackerToConditionMap) {
+        if (matcherCache[matcherIndex] == MatchingState::kMatched) {
             for (const int conditionIndex : conditionList) {
                 conditionToBeEvaluated[conditionIndex] = true;
+                conditionToTransformedLogEvents[conditionIndex] =
+                        matcherTransformations[matcherIndex];
             }
         }
     }
@@ -676,34 +682,38 @@
     // A bitmap to track if a condition has changed value.
     vector<uint8_t> changedCache(mAllConditionTrackers.size(), false);
     for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
-        if (conditionToBeEvaluated[i] == false) {
+        if (!conditionToBeEvaluated[i]) {
             continue;
         }
         sp<ConditionTracker>& condition = mAllConditionTrackers[i];
-        condition->evaluateCondition(event, matcherCache, mAllConditionTrackers, conditionCache,
-                                     changedCache);
+        const LogEvent& conditionEvent = conditionToTransformedLogEvents[i] == nullptr
+                                                 ? event
+                                                 : *conditionToTransformedLogEvents[i];
+        condition->evaluateCondition(conditionEvent, matcherCache, mAllConditionTrackers,
+                                     conditionCache, changedCache);
     }
 
     for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
-        if (changedCache[i] == false) {
+        if (!changedCache[i]) {
             continue;
         }
-        auto pair = mConditionToMetricMap.find(i);
-        if (pair != mConditionToMetricMap.end()) {
-            auto& metricList = pair->second;
-            for (auto metricIndex : metricList) {
-                // Metric cares about non sliced condition, and it's changed.
-                // Push the new condition to it directly.
-                if (!mAllMetricProducers[metricIndex]->isConditionSliced()) {
-                    mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i],
-                                                                         eventTimeNs);
-                    // Metric cares about sliced conditions, and it may have changed. Send
-                    // notification, and the metric can query the sliced conditions that are
-                    // interesting to it.
-                } else {
-                    mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(conditionCache[i],
-                                                                                 eventTimeNs);
-                }
+        auto it = mConditionToMetricMap.find(i);
+        if (it == mConditionToMetricMap.end()) {
+            continue;
+        }
+        auto& metricList = it->second;
+        for (auto metricIndex : metricList) {
+            // Metric cares about non sliced condition, and it's changed.
+            // Push the new condition to it directly.
+            if (!mAllMetricProducers[metricIndex]->isConditionSliced()) {
+                mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i],
+                                                                     eventTimeNs);
+                // Metric cares about sliced conditions, and it may have changed. Send
+                // notification, and the metric can query the sliced conditions that are
+                // interesting to it.
+            } else {
+                mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(conditionCache[i],
+                                                                             eventTimeNs);
             }
         }
     }
@@ -712,13 +722,16 @@
         if (matcherCache[i] == MatchingState::kMatched) {
             StatsdStats::getInstance().noteMatcherMatched(mConfigKey,
                                                           mAllAtomMatchingTrackers[i]->getId());
-            auto pair = mTrackerToMetricMap.find(i);
-            if (pair != mTrackerToMetricMap.end()) {
-                auto& metricList = pair->second;
-                for (const int metricIndex : metricList) {
-                    // pushed metrics are never scheduled pulls
-                    mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event);
-                }
+            auto it = mTrackerToMetricMap.find(i);
+            if (it == mTrackerToMetricMap.end()) {
+                continue;
+            }
+            auto& metricList = it->second;
+            const LogEvent& metricEvent =
+                    matcherTransformations[i] == nullptr ? event : *matcherTransformations[i];
+            for (const int metricIndex : metricList) {
+                // pushed metrics are never scheduled pulls
+                mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, metricEvent);
             }
         }
     }
diff --git a/statsd/src/metrics/NumericValueMetricProducer.cpp b/statsd/src/metrics/NumericValueMetricProducer.cpp
index bc17209..88e0975 100644
--- a/statsd/src/metrics/NumericValueMetricProducer.cpp
+++ b/statsd/src/metrics/NumericValueMetricProducer.cpp
@@ -267,15 +267,17 @@
         // before calculating the diff between sums of consecutive pulls.
         std::unordered_map<HashableDimensionKey, pair<LogEvent, vector<int>>> aggregateEvents;
         for (const auto& data : allData) {
-            if (mEventMatcherWizard->matchLogEvent(*data, mWhatMatcherIndex) !=
-                MatchingState::kMatched) {
+            const auto [matchResult, transformedEvent] =
+                    mEventMatcherWizard->matchLogEvent(*data, mWhatMatcherIndex);
+            if (matchResult != MatchingState::kMatched) {
                 continue;
             }
 
             // Get dimensions_in_what key and value indices.
             HashableDimensionKey dimensionsInWhat;
             vector<int> valueIndices(mFieldMatchers.size(), -1);
-            if (!filterValues(mDimensionsInWhat, mFieldMatchers, data->getValues(),
+            const LogEvent& eventRef = transformedEvent == nullptr ? *data : *transformedEvent;
+            if (!filterValues(mDimensionsInWhat, mFieldMatchers, eventRef.getValues(),
                               dimensionsInWhat, valueIndices)) {
                 StatsdStats::getInstance().noteBadValueType(mMetricId);
             }
@@ -285,9 +287,9 @@
             if (it == aggregateEvents.end()) {
                 aggregateEvents.emplace(std::piecewise_construct,
                                         std::forward_as_tuple(dimensionsInWhat),
-                                        std::forward_as_tuple(*data, valueIndices));
+                                        std::forward_as_tuple(eventRef, valueIndices));
             } else {
-                combineValueFields(it->second, *data, valueIndices);
+                combineValueFields(it->second, eventRef, valueIndices);
             }
         }
 
@@ -297,9 +299,10 @@
         }
     } else {
         for (const auto& data : allData) {
-            if (mEventMatcherWizard->matchLogEvent(*data, mWhatMatcherIndex) ==
-                MatchingState::kMatched) {
-                LogEvent localCopy = *data;
+            const auto [matchResult, transformedEvent] =
+                    mEventMatcherWizard->matchLogEvent(*data, mWhatMatcherIndex);
+            if (matchResult == MatchingState::kMatched) {
+                LogEvent localCopy = transformedEvent == nullptr ? *data : *transformedEvent;
                 localCopy.setElapsedTimestampNs(eventElapsedTimeNs);
                 onMatchedLogEventLocked(mWhatMatcherIndex, localCopy);
             }
diff --git a/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index 908fee1..18d7a32 100644
--- a/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -203,8 +203,9 @@
     std::fill(cycleTracker.begin(), cycleTracker.end(), false);
     for (size_t matcherIndex = 0; matcherIndex < newAtomMatchingTrackers.size(); matcherIndex++) {
         auto& matcher = newAtomMatchingTrackers[matcherIndex];
-        invalidConfigReason = matcher->init(matcherIndex, matcherProtos, newAtomMatchingTrackers,
-                                            newAtomMatchingTrackerMap, cycleTracker);
+        const auto [invalidConfigReason, _] =
+                matcher->init(matcherIndex, matcherProtos, newAtomMatchingTrackers,
+                              newAtomMatchingTrackerMap, cycleTracker);
         if (invalidConfigReason.has_value()) {
             return invalidConfigReason;
         }
diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 0feb793..673ff45 100644
--- a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -64,6 +64,71 @@
     return true;
 }
 
+// DFS for ensuring there is no
+// 1. value matching in the FieldValueMatcher tree with Position::ALL.
+// 2. string replacement in the FieldValueMatcher tree without a value matcher with Position::ANY.
+// Using vector to keep track of visited FieldValueMatchers since we expect number of
+// FieldValueMatchers to be low.
+optional<InvalidConfigReasonEnum> validateFvmPositionAllAndAny(
+        const FieldValueMatcher& fvm, bool inPositionAll, bool inPositionAny,
+        vector<FieldValueMatcher const*>& visited) {
+    visited.push_back(&fvm);
+    inPositionAll = inPositionAll || fvm.position() == Position::ALL;
+    inPositionAny = inPositionAny || fvm.position() == Position::ANY;
+    if (fvm.value_matcher_case() == FieldValueMatcher::kMatchesTuple) {
+        for (const FieldValueMatcher& childFvm : fvm.matches_tuple().field_value_matcher()) {
+            if (std::find(visited.cbegin(), visited.cend(), &childFvm) != visited.cend()) {
+                continue;
+            }
+            const optional<InvalidConfigReasonEnum> reasonEnum =
+                    validateFvmPositionAllAndAny(childFvm, inPositionAll, inPositionAny, visited);
+            if (reasonEnum != nullopt) {
+                return reasonEnum;
+            }
+        }
+        return nullopt;
+    }
+    if (inPositionAll && fvm.value_matcher_case() != FieldValueMatcher::VALUE_MATCHER_NOT_SET) {
+        // value_matcher is set to something other than matches_tuple with Position::ALL
+        return INVALID_CONFIG_REASON_MATCHER_VALUE_MATCHER_WITH_POSITION_ALL;
+    }
+    if (inPositionAny && fvm.value_matcher_case() == FieldValueMatcher::VALUE_MATCHER_NOT_SET &&
+        fvm.has_replace_string()) {
+        // value_matcher is not set and there is a string replacement with Position::ANY
+        return INVALID_CONFIG_REASON_MATCHER_STRING_REPLACE_WITH_NO_VALUE_MATCHER_WITH_POSITION_ANY;
+    }
+    return nullopt;
+}
+
+optional<InvalidConfigReason> validateSimpleAtomMatcher(int64_t matcherId,
+                                                        const SimpleAtomMatcher& simpleMatcher) {
+    for (const FieldValueMatcher& fvm : simpleMatcher.field_value_matcher()) {
+        if (fvm.value_matcher_case() == FieldValueMatcher::VALUE_MATCHER_NOT_SET &&
+            !fvm.has_replace_string()) {
+            return createInvalidConfigReasonWithMatcher(
+                    INVALID_CONFIG_REASON_MATCHER_NO_VALUE_MATCHER_NOR_STRING_REPLACER, matcherId);
+        } else if (fvm.has_replace_string() &&
+                   !(fvm.value_matcher_case() == FieldValueMatcher::VALUE_MATCHER_NOT_SET ||
+                     fvm.value_matcher_case() == FieldValueMatcher::kEqString ||
+                     fvm.value_matcher_case() == FieldValueMatcher::kEqAnyString ||
+                     fvm.value_matcher_case() == FieldValueMatcher::kNeqAnyString ||
+                     fvm.value_matcher_case() == FieldValueMatcher::kEqWildcardString ||
+                     fvm.value_matcher_case() == FieldValueMatcher::kEqAnyWildcardString ||
+                     fvm.value_matcher_case() == FieldValueMatcher::kNeqAnyWildcardString)) {
+            return createInvalidConfigReasonWithMatcher(
+                    INVALID_CONFIG_REASON_MATCHER_INVALID_VALUE_MATCHER_WITH_STRING_REPLACE,
+                    matcherId);
+        }
+        vector<FieldValueMatcher const*> visited;
+        const optional<InvalidConfigReasonEnum> reasonEnum = validateFvmPositionAllAndAny(
+                fvm, false /* inPositionAll */, false /* inPositionAny */, visited);
+        if (reasonEnum != nullopt) {
+            return createInvalidConfigReasonWithMatcher(*reasonEnum, matcherId);
+        }
+    }
+    return nullopt;
+}
+
 }  // namespace
 
 sp<AtomMatchingTracker> createAtomMatchingTracker(
@@ -79,6 +144,12 @@
     uint64_t protoHash = Hash64(serializedMatcher);
     switch (logMatcher.contents_case()) {
         case AtomMatcher::ContentsCase::kSimpleAtomMatcher: {
+            invalidConfigReason =
+                    validateSimpleAtomMatcher(logMatcher.id(), logMatcher.simple_atom_matcher());
+            if (invalidConfigReason != nullopt) {
+                ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id());
+                return nullptr;
+            }
             sp<AtomMatchingTracker> simpleAtomMatcher = new SimpleAtomMatchingTracker(
                     logMatcher.id(), protoHash, logMatcher.simple_atom_matcher(), uidMap);
             return simpleAtomMatcher;
@@ -1342,8 +1413,9 @@
     vector<uint8_t> stackTracker2(allAtomMatchingTrackers.size(), false);
     for (size_t matcherIndex = 0; matcherIndex < allAtomMatchingTrackers.size(); matcherIndex++) {
         auto& matcher = allAtomMatchingTrackers[matcherIndex];
-        invalidConfigReason = matcher->init(matcherIndex, matcherConfigs, allAtomMatchingTrackers,
-                                            atomMatchingTrackerMap, stackTracker2);
+        const auto [invalidConfigReason, _] =
+                matcher->init(matcherIndex, matcherConfigs, allAtomMatchingTrackers,
+                              atomMatchingTrackerMap, stackTracker2);
         if (invalidConfigReason.has_value()) {
             return invalidConfigReason;
         }
diff --git a/statsd/src/shell/ShellSubscriberClient.cpp b/statsd/src/shell/ShellSubscriberClient.cpp
index aaa4126..4ae8e93 100644
--- a/statsd/src/shell/ShellSubscriberClient.cpp
+++ b/statsd/src/shell/ShellSubscriberClient.cpp
@@ -177,23 +177,25 @@
 bool ShellSubscriberClient::writeEventToProtoIfMatched(const LogEvent& event,
                                                        const SimpleAtomMatcher& matcher,
                                                        const sp<UidMap>& uidMap) {
-    if (!matchesSimple(uidMap, matcher, event)) {
+    auto [matched, transformedEvent] = matchesSimple(mUidMap, matcher, event);
+    if (!matched) {
         return false;
     }
+    const LogEvent& eventRef = transformedEvent == nullptr ? event : *transformedEvent;
 
     // Cache atom event in mProtoOut.
     uint64_t atomToken = mProtoOut.start(util::FIELD_TYPE_MESSAGE | util::FIELD_COUNT_REPEATED |
                                          FIELD_ID_SHELL_DATA__ATOM);
-    event.ToProto(mProtoOut);
+    eventRef.ToProto(mProtoOut);
     mProtoOut.end(atomToken);
 
-    const int64_t timestampNs = truncateTimestampIfNecessary(event);
+    const int64_t timestampNs = truncateTimestampIfNecessary(eventRef);
     mProtoOut.write(util::FIELD_TYPE_INT64 | util::FIELD_COUNT_REPEATED |
                             FIELD_ID_SHELL_DATA__ELAPSED_TIMESTAMP_NANOS,
                     static_cast<long long>(timestampNs));
 
     // Update byte size of cached data.
-    mCacheSize += getSize(event.getValues()) + sizeof(timestampNs);
+    mCacheSize += getSize(eventRef.getValues()) + sizeof(timestampNs);
 
     return true;
 }
diff --git a/statsd/src/statsd_config.proto b/statsd/src/statsd_config.proto
index 9c937a5..a7eef69 100644
--- a/statsd/src/statsd_config.proto
+++ b/statsd/src/statsd_config.proto
@@ -56,6 +56,14 @@
   repeated FieldMatcher child = 3;
 }
 
+message StringReplacer {
+  // Regex for matching the string.
+  optional string regex = 1;
+
+  // String with which to replace the matched string.
+  optional string replacement = 2;
+}
+
 message FieldValueMatcher {
   optional int32 field = 1;
 
@@ -85,6 +93,11 @@
     StringListMatcher eq_any_wildcard_string = 18;
     StringListMatcher neq_any_wildcard_string = 19;
   }
+
+  // Can only be present if either:
+  // 1. value_matcher is not set.
+  // 2. value_matcher is set to one that is applicable to string fields.
+  optional StringReplacer replace_string = 20;
 }
 
 message MessageMatcher {
diff --git a/statsd/tests/LogEntryMatcher_test.cpp b/statsd/tests/LogEntryMatcher_test.cpp
index 5c098c2..2a0be07 100644
--- a/statsd/tests/LogEntryMatcher_test.cpp
+++ b/statsd/tests/LogEntryMatcher_test.cpp
@@ -24,6 +24,7 @@
 #include "statsd_test_util.h"
 
 using namespace android::os::statsd;
+using std::shared_ptr;
 using std::unordered_map;
 using std::vector;
 
@@ -31,7 +32,7 @@
 const int32_t TAG_ID_2 = 28;  // hardcoded tag of atom with uid field
 const int FIELD_ID_1 = 1;
 const int FIELD_ID_2 = 2;
-const int FIELD_ID_3 = 2;
+const int FIELD_ID_3 = 3;
 
 const int ATTRIBUTION_UID_FIELD_ID = 1;
 const int ATTRIBUTION_TAG_FIELD_ID = 2;
@@ -151,11 +152,11 @@
     makeIntLogEvent(&event, TAG_ID, 0, 11);
 
     // Test
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Wrong tag id.
     simpleMatcher->set_atom_id(TAG_ID + 1);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestAttributionMatcher) {
@@ -186,43 +187,43 @@
     fieldMatcher->set_eq_string("some value");
 
     // Tag not matched.
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "location3");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "location1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match last node.
     attributionMatcher->set_position(Position::LAST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "location3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match any node.
     attributionMatcher->set_position(Position::ANY);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "location1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "location2");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "location3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "location4");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Attribution match but primitive field not match.
     attributionMatcher->set_position(Position::ANY);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "location2");
     fieldMatcher->set_eq_string("wrong value");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldMatcher->set_eq_string("some value");
 
@@ -232,7 +233,7 @@
             ATTRIBUTION_UID_FIELD_ID);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg0");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     UidData uidData;
     *uidData.add_app_info() = createApplicationInfo(/*uid*/ 1111, /*version*/ 1, "v1", "pkg0");
@@ -243,47 +244,47 @@
 
     uidMap->updateMap(1, uidData);
 
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg2");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg0");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     attributionMatcher->set_position(Position::FIRST);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg0");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg3");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg2");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     attributionMatcher->set_position(Position::LAST);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg0");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg2");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg1");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Uid + tag.
     attributionMatcher->set_position(Position::ANY);
@@ -293,96 +294,96 @@
             "pkg0");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg1");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg1");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location2");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg2");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg3");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg3");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location1");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     attributionMatcher->set_position(Position::FIRST);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg0");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg1");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg1");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location2");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg2");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location3");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg3");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location3");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg3");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location1");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     attributionMatcher->set_position(Position::LAST);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg0");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location1");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg1");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location1");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg1");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location2");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg2");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg3");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string(
             "pkg3");
     attributionMatcher->mutable_matches_tuple()->mutable_field_value_matcher(1)->set_eq_string(
             "location1");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestUidFieldMatcher) {
@@ -407,7 +408,7 @@
     // Make event without is_uid annotation.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeIntLogEvent(&event1, TAG_ID, 0, 1111);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1).matched);
 
     // Make event with is_uid annotation.
     LogEvent event2(/*uid=*/0, /*pid=*/0);
@@ -416,12 +417,12 @@
 
     // Event has is_uid annotation, so mapping from uid to package name occurs.
     simpleMatcher->set_atom_id(TAG_ID_2);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 
     // Event has is_uid annotation, but uid maps to different package name.
     simpleMatcher->mutable_field_value_matcher(0)->set_eq_string(
             "pkg2");  // package names are normalized
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 }
 
 TEST(AtomMatcherTest, TestRepeatedUidFieldMatcher) {
@@ -450,35 +451,35 @@
 
     fieldValueMatcher->set_position(Position::FIRST);
     fieldValueMatcher->set_eq_string("pkg0");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1).matched);
 
     fieldValueMatcher->set_position(Position::LAST);
     fieldValueMatcher->set_eq_string("pkg1");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1).matched);
 
     fieldValueMatcher->set_position(Position::ANY);
     fieldValueMatcher->set_eq_string("pkg2");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1).matched);
 
     // is_uid annotation, mapping from uid to package name.
     LogEvent event2(/*uid=*/0, /*pid=*/0);
     makeRepeatedUidLogEvent(&event2, TAG_ID, intArray);
 
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
     fieldValueMatcher->set_eq_string("pkg0");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
     fieldValueMatcher->set_eq_string("pkg1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 
     fieldValueMatcher->set_position(Position::ANY);
     fieldValueMatcher->set_eq_string("pkg");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
     fieldValueMatcher->set_eq_string("pkg2");  // package names are normalized
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 }
 
 TEST(AtomMatcherTest, TestNeqAnyStringMatcher_SingleString) {
@@ -498,17 +499,17 @@
     // First string matched.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event1, TAG_ID, 0, "some value");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1).matched);
 
     // Second string matched.
     LogEvent event2(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event2, TAG_ID, 0, "another value");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 
     // No strings matched.
     LogEvent event3(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event3, TAG_ID, 0, "foo");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3).matched);
 }
 
 TEST(AtomMatcherTest, TestNeqAnyStringMatcher_AttributionUids) {
@@ -551,26 +552,26 @@
     fieldMatcher->set_field(FIELD_ID_2);
     fieldMatcher->set_eq_string("some value");
 
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     neqStringList->Clear();
     neqStringList->add_str_value("pkg1");
     neqStringList->add_str_value("pkg3");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     attributionMatcher->set_position(Position::ANY);
     neqStringList->Clear();
     neqStringList->add_str_value("maps.com");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     neqStringList->Clear();
     neqStringList->add_str_value("PkG3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     attributionMatcher->set_position(Position::LAST);
     neqStringList->Clear();
     neqStringList->add_str_value("AID_STATSD");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestEqAnyStringMatcher) {
@@ -613,29 +614,29 @@
     fieldMatcher->set_field(FIELD_ID_2);
     fieldMatcher->set_eq_string("some value");
 
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     attributionMatcher->set_position(Position::ANY);
     eqStringList->Clear();
     eqStringList->add_str_value("AID_STATSD");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     eqStringList->Clear();
     eqStringList->add_str_value("pkg1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     auto normalStringField = fieldMatcher->mutable_eq_any_string();
     normalStringField->add_str_value("some value123");
     normalStringField->add_str_value("some value");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     normalStringField->Clear();
     normalStringField->add_str_value("AID_STATSD");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     eqStringList->Clear();
     eqStringList->add_str_value("maps.com");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestBoolMatcher) {
@@ -656,19 +657,19 @@
     // Test
     keyValue1->set_eq_bool(true);
     keyValue2->set_eq_bool(false);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     keyValue1->set_eq_bool(false);
     keyValue2->set_eq_bool(false);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     keyValue1->set_eq_bool(false);
     keyValue2->set_eq_bool(true);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     keyValue1->set_eq_bool(true);
     keyValue2->set_eq_bool(true);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestStringMatcher) {
@@ -686,7 +687,7 @@
     makeStringLogEvent(&event, TAG_ID, 0, "some value");
 
     // Test
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestIntMatcher_EmptyRepeatedField) {
@@ -706,16 +707,16 @@
     // Match first int.
     fieldValueMatcher->set_position(Position::FIRST);
     fieldValueMatcher->set_eq_int(9);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match last int.
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match any int.
     fieldValueMatcher->set_position(Position::ANY);
     fieldValueMatcher->set_eq_int(13);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestIntMatcher_RepeatedIntField) {
@@ -736,28 +737,28 @@
     fieldValueMatcher->set_field(FIELD_ID_1);
     fieldValueMatcher->set_position(Position::FIRST);
     fieldValueMatcher->set_eq_int(9);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_eq_int(21);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match last int.
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_eq_int(9);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match any int.
     fieldValueMatcher->set_position(Position::ANY);
     fieldValueMatcher->set_eq_int(13);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_eq_int(21);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_eq_int(9);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestLtIntMatcher_RepeatedIntField) {
@@ -778,34 +779,34 @@
     fieldValueMatcher->set_field(FIELD_ID_1);
     fieldValueMatcher->set_position(Position::FIRST);
     fieldValueMatcher->set_lt_int(9);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_lt_int(21);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_lt_int(23);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match last int.
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_lt_int(9);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_lt_int(8);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match any int.
     fieldValueMatcher->set_position(Position::ANY);
     fieldValueMatcher->set_lt_int(21);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_lt_int(8);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_lt_int(23);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestStringMatcher_RepeatedStringField) {
@@ -826,31 +827,31 @@
     fieldValueMatcher->set_field(FIELD_ID_1);
     fieldValueMatcher->set_position(Position::FIRST);
     fieldValueMatcher->set_eq_string("str2");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_eq_string("str1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match last int.
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_eq_string("str3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match any int.
     fieldValueMatcher->set_position(Position::ANY);
     fieldValueMatcher->set_eq_string("str4");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_eq_string("str1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_eq_string("str2");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     fieldValueMatcher->set_eq_string("str3");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestEqAnyStringMatcher_RepeatedStringField) {
@@ -871,43 +872,43 @@
     StringListMatcher* eqStringList = fieldValueMatcher->mutable_eq_any_string();
 
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::ANY);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     eqStringList->add_str_value("str4");
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::ANY);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     eqStringList->add_str_value("str2");
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::ANY);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     eqStringList->add_str_value("str3");
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::ANY);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     eqStringList->add_str_value("str1");
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::ANY);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestNeqAnyStringMatcher_RepeatedStringField) {
@@ -928,43 +929,43 @@
     StringListMatcher* neqStringList = fieldValueMatcher->mutable_neq_any_string();
 
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::ANY);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     neqStringList->add_str_value("str4");
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::ANY);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     neqStringList->add_str_value("str2");
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::ANY);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     neqStringList->add_str_value("str3");
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::ANY);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     neqStringList->add_str_value("str1");
     fieldValueMatcher->set_position(Position::FIRST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::LAST);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     fieldValueMatcher->set_position(Position::ANY);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestMultiFieldsMatcher) {
@@ -985,15 +986,15 @@
     // Test
     keyValue1->set_eq_int(2);
     keyValue2->set_eq_int(3);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     keyValue1->set_eq_int(2);
     keyValue2->set_eq_int(4);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     keyValue1->set_eq_int(4);
     keyValue2->set_eq_int(3);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestIntComparisonMatcher) {
@@ -1014,43 +1015,43 @@
 
     // eq_int
     keyValue->set_eq_int(10);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     keyValue->set_eq_int(11);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     keyValue->set_eq_int(12);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // lt_int
     keyValue->set_lt_int(10);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     keyValue->set_lt_int(11);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     keyValue->set_lt_int(12);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // lte_int
     keyValue->set_lte_int(10);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     keyValue->set_lte_int(11);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     keyValue->set_lte_int(12);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // gt_int
     keyValue->set_gt_int(10);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     keyValue->set_gt_int(11);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     keyValue->set_gt_int(12);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // gte_int
     keyValue->set_gte_int(10);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     keyValue->set_gte_int(11);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
     keyValue->set_gte_int(12);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 }
 
 TEST(AtomMatcherTest, TestFloatComparisonMatcher) {
@@ -1066,20 +1067,20 @@
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeFloatLogEvent(&event1, TAG_ID, 0, 10.1f);
     keyValue->set_lt_float(10.0);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1).matched);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
     makeFloatLogEvent(&event2, TAG_ID, 0, 9.9f);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 
     LogEvent event3(/*uid=*/0, /*pid=*/0);
     makeFloatLogEvent(&event3, TAG_ID, 0, 10.1f);
     keyValue->set_gt_float(10.0);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3).matched);
 
     LogEvent event4(/*uid=*/0, /*pid=*/0);
     makeFloatLogEvent(&event4, TAG_ID, 0, 9.9f);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4).matched);
 }
 
 // Helper for the composite matchers.
@@ -1226,17 +1227,17 @@
     // Event without is_uid annotation.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeIntLogEvent(&event1, TAG_ID, 0, 1111);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1).matched);
 
     // Event where mapping from uid to package name occurs.
     LogEvent event2(/*uid=*/0, /*pid=*/0);
     makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID, 1111, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 
     // Event where uid maps to package names that don't fit wildcard pattern.
     LogEvent event3(/*uid=*/0, /*pid=*/0);
     makeIntWithBoolAnnotationLogEvent(&event3, TAG_ID, 3333, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event3));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event3).matched);
 
     // Update matcher to match one AID
     simpleMatcher->mutable_field_value_matcher(0)->set_eq_wildcard_string(
@@ -1245,12 +1246,12 @@
     // Event where mapping from uid to aid doesn't fit wildcard pattern.
     LogEvent event4(/*uid=*/0, /*pid=*/0);
     makeIntWithBoolAnnotationLogEvent(&event4, TAG_ID, 1005, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4).matched);
 
     // Event where mapping from uid to aid does fit wildcard pattern.
     LogEvent event5(/*uid=*/0, /*pid=*/0);
     makeIntWithBoolAnnotationLogEvent(&event5, TAG_ID, 1000, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event5));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event5).matched);
 
     // Update matcher to match multiple AIDs
     simpleMatcher->mutable_field_value_matcher(0)->set_eq_wildcard_string("AID_SDCARD_*");
@@ -1258,16 +1259,16 @@
     // Event where mapping from uid to aid doesn't fit wildcard pattern.
     LogEvent event6(/*uid=*/0, /*pid=*/0);
     makeIntWithBoolAnnotationLogEvent(&event6, TAG_ID, 1036, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event6));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event6).matched);
 
     // Event where mapping from uid to aid does fit wildcard pattern.
     LogEvent event7(/*uid=*/0, /*pid=*/0);
     makeIntWithBoolAnnotationLogEvent(&event7, TAG_ID, 1034, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event7));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event7).matched);
 
     LogEvent event8(/*uid=*/0, /*pid=*/0);
     makeIntWithBoolAnnotationLogEvent(&event8, TAG_ID, 1035, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event8));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event8).matched);
 }
 
 TEST(AtomMatcherTest, TestWildcardStringMatcher) {
@@ -1284,55 +1285,57 @@
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event1, TAG_ID, 0, "test.string:test_0");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event1));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event1).matched);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event2, TAG_ID, 0, "test.string:test_19");
-    EXPECT_FALSE(
-            matchesSimple(uidMap, *simpleMatcher, event2));  // extra character at end of string
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2)
+                         .matched);  // extra character at end of string
 
     LogEvent event3(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event3, TAG_ID, 0, "extra.test.string:test_1");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher,
-                               event3));  // extra characters at beginning of string
+                               event3)
+                         .matched);  // extra characters at beginning of string
 
     LogEvent event4(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event4, TAG_ID, 0, "test.string:test_");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher,
-                               event4));  // missing character from 0-9 at end of string
+                               event4)
+                         .matched);  // missing character from 0-9 at end of string
 
     LogEvent event5(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event5, TAG_ID, 0, "est.string:test_1");
-    EXPECT_FALSE(
-            matchesSimple(uidMap, *simpleMatcher, event5));  // missing 't' at beginning of string
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event5)
+                         .matched);  // missing 't' at beginning of string
 
     LogEvent event6(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event6, TAG_ID, 0, "test.string:test_1extra");
-    EXPECT_FALSE(
-            matchesSimple(uidMap, *simpleMatcher, event6));  // extra characters at end of string
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event6)
+                         .matched);  // extra characters at end of string
 
     // Matches any string that contains "test.string:test_" + any extra characters before or after
     fieldValueMatcher->set_eq_wildcard_string("*test.string:test_*");
 
     LogEvent event7(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event7, TAG_ID, 0, "test.string:test_");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event7));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event7).matched);
 
     LogEvent event8(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event8, TAG_ID, 0, "extra.test.string:test_");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event8));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event8).matched);
 
     LogEvent event9(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event9, TAG_ID, 0, "test.string:test_extra");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event9));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event9).matched);
 
     LogEvent event10(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event10, TAG_ID, 0, "est.string:test_");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event10));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event10).matched);
 
     LogEvent event11(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event11, TAG_ID, 0, "test.string:test");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event11));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event11).matched);
 }
 
 TEST(AtomMatcherTest, TestEqAnyWildcardStringMatcher) {
@@ -1352,17 +1355,17 @@
     // First wildcard pattern matched.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event1, TAG_ID, 0, "first_string_1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event1));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event1).matched);
 
     // Second wildcard pattern matched.
     LogEvent event2(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event2, TAG_ID, 0, "second_string_1");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 
     // No wildcard patterns matched.
     LogEvent event3(/*uid=*/0, /*pid=*/0);
     makeStringLogEvent(&event3, TAG_ID, 0, "third_string_1");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event3));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event3).matched);
 }
 
 TEST(AtomMatcherTest, TestNeqAnyWildcardStringMatcher) {
@@ -1389,27 +1392,27 @@
 
     // First tag is not matched. neq string list {"tag"}
     neqWildcardStrList->add_str_value("tag");
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // First tag is matched. neq string list {"tag", "location_*"}
     neqWildcardStrList->add_str_value("location_*");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match last tag.
     attributionMatcher->set_position(Position::LAST);
 
     // Last tag is not matched. neq string list {"tag", "location_*"}
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Last tag is matched. neq string list {"tag", "location_*", "location*"}
     neqWildcardStrList->add_str_value("location*");
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match any tag.
     attributionMatcher->set_position(Position::ANY);
 
     // All tags are matched. neq string list {"tag", "location_*", "location*"}
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Set up another log event.
     std::vector<string> attributionTags2 = {"location_1", "location", "string"};
@@ -1417,7 +1420,7 @@
     makeAttributionLogEvent(&event2, TAG_ID, 0, attributionUids, attributionTags2, "some value");
 
     // Tag "string" is not matched. neq string list {"tag", "location_*", "location*"}
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 }
 
 TEST(AtomMatcherTest, TestEqAnyIntMatcher) {
@@ -1437,17 +1440,17 @@
     // First int matched.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeIntLogEvent(&event1, TAG_ID, 0, 3);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event1));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event1).matched);
 
     // Second int matched.
     LogEvent event2(/*uid=*/0, /*pid=*/0);
     makeIntLogEvent(&event2, TAG_ID, 0, 5);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 
     // No ints matched.
     LogEvent event3(/*uid=*/0, /*pid=*/0);
     makeIntLogEvent(&event3, TAG_ID, 0, 4);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event3));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event3).matched);
 }
 
 TEST(AtomMatcherTest, TestNeqAnyIntMatcher) {
@@ -1473,31 +1476,569 @@
 
     // First uid is not matched. neq int list {4444}
     neqIntList->add_int_value(4444);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // First uid is matched. neq int list {4444, 1111}
     neqIntList->add_int_value(1111);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match last uid.
     attributionMatcher->set_position(Position::LAST);
 
     // Last uid is not matched. neq int list {4444, 1111}
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Last uid is matched. neq int list {4444, 1111, 3333}
     neqIntList->add_int_value(3333);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // Match any uid.
     attributionMatcher->set_position(Position::ANY);
 
     // Uid 2222 is not matched. neq int list {4444, 1111, 3333}
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event).matched);
 
     // All uids are matched. neq int list {4444, 1111, 3333, 2222}
     neqIntList->add_int_value(2222);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event).matched);
+}
+
+TEST(AtomMatcherTest, TestStringReplaceRoot) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value123");
+
+    // Set up the matcher. Replace second field.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(FIELD_ID_2);
+    StringReplacer* stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    const auto [hasMatched, transformedEvent] =
+            matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+    EXPECT_TRUE(hasMatched);
+    ASSERT_NE(transformedEvent, nullptr);
+
+    const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+    ASSERT_EQ(fieldValues.size(), 7);
+    EXPECT_EQ(fieldValues[0].mValue.int_value, 1111);
+    EXPECT_EQ(fieldValues[1].mValue.str_value, "location1");
+    EXPECT_EQ(fieldValues[2].mValue.int_value, 2222);
+    EXPECT_EQ(fieldValues[3].mValue.str_value, "location2");
+    EXPECT_EQ(fieldValues[4].mValue.int_value, 3333);
+    EXPECT_EQ(fieldValues[5].mValue.str_value, "location3");
+    EXPECT_EQ(fieldValues[6].mValue.str_value, "some value");
+}
+
+TEST(AtomMatcherTest, TestStringReplaceAttributionTagFirst) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value123");
+
+    // Set up the matcher. Replace first attribution tag.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* attributionFvm =
+            matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    attributionFvm->set_field(FIELD_ID_1);
+    attributionFvm->set_position(Position::FIRST);
+    FieldValueMatcher* attributionTagFvm =
+            attributionFvm->mutable_matches_tuple()->add_field_value_matcher();
+    attributionTagFvm->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    StringReplacer* stringReplacer = attributionTagFvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    const auto [hasMatched, transformedEvent] =
+            matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+    EXPECT_TRUE(hasMatched);
+    ASSERT_NE(transformedEvent, nullptr);
+    const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+    ASSERT_EQ(fieldValues.size(), 7);
+    EXPECT_EQ(fieldValues[0].mValue.int_value, 1111);
+    EXPECT_EQ(fieldValues[1].mValue.str_value, "location");
+    EXPECT_EQ(fieldValues[2].mValue.int_value, 2222);
+    EXPECT_EQ(fieldValues[3].mValue.str_value, "location2");
+    EXPECT_EQ(fieldValues[4].mValue.int_value, 3333);
+    EXPECT_EQ(fieldValues[5].mValue.str_value, "location3");
+    EXPECT_EQ(fieldValues[6].mValue.str_value, "some value123");
+}
+
+TEST(AtomMatcherTest, TestStringReplaceAttributionTagLast) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value123");
+
+    // Set up the matcher. Replace last attribution tag.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* attributionFvm =
+            matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    attributionFvm->set_field(FIELD_ID_1);
+    attributionFvm->set_position(Position::LAST);
+    FieldValueMatcher* attributionTagFvm =
+            attributionFvm->mutable_matches_tuple()->add_field_value_matcher();
+    attributionTagFvm->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    StringReplacer* stringReplacer = attributionTagFvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    const auto [hasMatched, transformedEvent] =
+            matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+    EXPECT_TRUE(hasMatched);
+    ASSERT_NE(transformedEvent, nullptr);
+
+    const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+    ASSERT_EQ(fieldValues.size(), 7);
+    EXPECT_EQ(fieldValues[0].mValue.int_value, 1111);
+    EXPECT_EQ(fieldValues[1].mValue.str_value, "location1");
+    EXPECT_EQ(fieldValues[2].mValue.int_value, 2222);
+    EXPECT_EQ(fieldValues[3].mValue.str_value, "location2");
+    EXPECT_EQ(fieldValues[4].mValue.int_value, 3333);
+    EXPECT_EQ(fieldValues[5].mValue.str_value, "location");
+    EXPECT_EQ(fieldValues[6].mValue.str_value, "some value123");
+}
+
+TEST(AtomMatcherTest, TestStringReplaceAttributionTagAll) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value123");
+
+    // Set up the matcher. Replace all attribution tags.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* attributionFvm =
+            matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    attributionFvm->set_field(FIELD_ID_1);
+    attributionFvm->set_position(Position::ALL);
+    FieldValueMatcher* attributionTagFvm =
+            attributionFvm->mutable_matches_tuple()->add_field_value_matcher();
+    attributionTagFvm->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    StringReplacer* stringReplacer = attributionTagFvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    const auto [hasMatched, transformedEvent] =
+            matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+    EXPECT_TRUE(hasMatched);
+    ASSERT_NE(transformedEvent, nullptr);
+
+    const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+    ASSERT_EQ(fieldValues.size(), 7);
+    EXPECT_EQ(fieldValues[0].mValue.int_value, 1111);
+    EXPECT_EQ(fieldValues[1].mValue.str_value, "location");
+    EXPECT_EQ(fieldValues[2].mValue.int_value, 2222);
+    EXPECT_EQ(fieldValues[3].mValue.str_value, "location");
+    EXPECT_EQ(fieldValues[4].mValue.int_value, 3333);
+    EXPECT_EQ(fieldValues[5].mValue.str_value, "location");
+    EXPECT_EQ(fieldValues[6].mValue.str_value, "some value123");
+}
+
+TEST(AtomMatcherTest, TestStringReplaceNestedAllWithMultipleNestedStringFields) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value123");
+
+    // Manually change uid fields to string fields, as there is no direct way to create a
+    // LogEvent with multiple nested string fields.
+    (*event.getMutableValues())[0].mValue = android::os::statsd::Value("abc1");
+    (*event.getMutableValues())[2].mValue = android::os::statsd::Value("xyz2");
+    (*event.getMutableValues())[4].mValue = android::os::statsd::Value("abc3");
+
+    // Set up the matcher. Replace all attribution tags.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* attributionFvm =
+            matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    attributionFvm->set_field(FIELD_ID_1);
+    attributionFvm->set_position(Position::ALL);
+    FieldValueMatcher* attributionTagFvm =
+            attributionFvm->mutable_matches_tuple()->add_field_value_matcher();
+    attributionTagFvm->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    StringReplacer* stringReplacer = attributionTagFvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    const auto [hasMatched, transformedEvent] =
+            matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+    EXPECT_TRUE(hasMatched);
+    ASSERT_NE(transformedEvent, nullptr);
+
+    const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+    ASSERT_EQ(fieldValues.size(), 7);
+    EXPECT_EQ(fieldValues[0].mValue.str_value, "abc1");
+    EXPECT_EQ(fieldValues[1].mValue.str_value, "location");
+    EXPECT_EQ(fieldValues[2].mValue.str_value, "xyz2");
+    EXPECT_EQ(fieldValues[3].mValue.str_value, "location");
+    EXPECT_EQ(fieldValues[4].mValue.str_value, "abc3");
+    EXPECT_EQ(fieldValues[5].mValue.str_value, "location");
+    EXPECT_EQ(fieldValues[6].mValue.str_value, "some value123");
+}
+
+TEST(AtomMatcherTest, TestStringReplaceRootOnMatchedField) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the matcher. Replace second field and match on replaced field.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(FIELD_ID_2);
+    fvm->set_eq_string("bar");
+    StringReplacer* stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags,
+                                "some value123");
+
+        EXPECT_FALSE(matchesSimple(uidMap, matcher.simple_atom_matcher(), event).matched);
+    }
+
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "bar123");
+        const auto [hasMatched, transformedEvent] =
+                matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+        EXPECT_TRUE(hasMatched);
+        ASSERT_NE(transformedEvent, nullptr);
+        const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+        ASSERT_EQ(fieldValues.size(), 7);
+        EXPECT_EQ(fieldValues[0].mValue.int_value, 1111);
+        EXPECT_EQ(fieldValues[1].mValue.str_value, "location1");
+        EXPECT_EQ(fieldValues[2].mValue.int_value, 2222);
+        EXPECT_EQ(fieldValues[3].mValue.str_value, "location2");
+        EXPECT_EQ(fieldValues[4].mValue.int_value, 3333);
+        EXPECT_EQ(fieldValues[5].mValue.str_value, "location3");
+        EXPECT_EQ(fieldValues[6].mValue.str_value, "bar");
+    }
+}
+
+TEST(AtomMatcherTest, TestStringReplaceAttributionTagFirstOnMatchedField) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the matcher. Replace first attribution tag and match on that tag.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* attributionFvm =
+            matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    attributionFvm->set_field(FIELD_ID_1);
+    attributionFvm->set_position(Position::FIRST);
+    FieldValueMatcher* attributionTagFvm =
+            attributionFvm->mutable_matches_tuple()->add_field_value_matcher();
+    attributionTagFvm->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    attributionTagFvm->set_eq_string("bar");
+    StringReplacer* stringReplacer = attributionTagFvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags,
+                                "some value123");
+
+        EXPECT_FALSE(matchesSimple(uidMap, matcher.simple_atom_matcher(), event).matched);
+    }
+
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        attributionTags = {"bar1", "bar2", "bar3"};
+        makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "bar123");
+        const auto [hasMatched, transformedEvent] =
+                matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+        EXPECT_TRUE(hasMatched);
+        ASSERT_NE(transformedEvent, nullptr);
+        const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+        ASSERT_EQ(fieldValues.size(), 7);
+        EXPECT_EQ(fieldValues[0].mValue.int_value, 1111);
+        EXPECT_EQ(fieldValues[1].mValue.str_value, "bar");
+        EXPECT_EQ(fieldValues[2].mValue.int_value, 2222);
+        EXPECT_EQ(fieldValues[3].mValue.str_value, "bar2");
+        EXPECT_EQ(fieldValues[4].mValue.int_value, 3333);
+        EXPECT_EQ(fieldValues[5].mValue.str_value, "bar3");
+        EXPECT_EQ(fieldValues[6].mValue.str_value, "bar123");
+    }
+}
+
+TEST(AtomMatcherTest, TestStringReplaceAttributionTagLastOnMatchedField) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the matcher. Replace last attribution tag and match on that tag.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* attributionFvm =
+            matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    attributionFvm->set_field(FIELD_ID_1);
+    attributionFvm->set_position(Position::LAST);
+    FieldValueMatcher* attributionTagFvm =
+            attributionFvm->mutable_matches_tuple()->add_field_value_matcher();
+    attributionTagFvm->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    attributionTagFvm->set_eq_string("bar");
+    StringReplacer* stringReplacer = attributionTagFvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags,
+                                "some value123");
+
+        EXPECT_FALSE(matchesSimple(uidMap, matcher.simple_atom_matcher(), event).matched);
+    }
+
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        attributionTags = {"bar1", "bar2", "bar3"};
+        makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "bar123");
+        const auto [hasMatched, transformedEvent] =
+                matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+        EXPECT_TRUE(hasMatched);
+        ASSERT_NE(transformedEvent, nullptr);
+        const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+        ASSERT_EQ(fieldValues.size(), 7);
+        EXPECT_EQ(fieldValues[0].mValue.int_value, 1111);
+        EXPECT_EQ(fieldValues[1].mValue.str_value, "bar1");
+        EXPECT_EQ(fieldValues[2].mValue.int_value, 2222);
+        EXPECT_EQ(fieldValues[3].mValue.str_value, "bar2");
+        EXPECT_EQ(fieldValues[4].mValue.int_value, 3333);
+        EXPECT_EQ(fieldValues[5].mValue.str_value, "bar");
+        EXPECT_EQ(fieldValues[6].mValue.str_value, "bar123");
+    }
+}
+
+TEST(AtomMatcherTest, TestStringReplaceAttributionTagAnyOnMatchedField) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the matcher. Replace all attribution tags but match on any tag.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* attributionFvm =
+            matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    attributionFvm->set_field(FIELD_ID_1);
+    attributionFvm->set_position(Position::ANY);
+    FieldValueMatcher* attributionTagFvm =
+            attributionFvm->mutable_matches_tuple()->add_field_value_matcher();
+    attributionTagFvm->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    attributionTagFvm->set_eq_string("bar");
+    StringReplacer* stringReplacer = attributionTagFvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags,
+                                "some value123");
+
+        EXPECT_FALSE(matchesSimple(uidMap, matcher.simple_atom_matcher(), event).matched);
+    }
+
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        attributionTags = {"foo1", "bar2", "foo3"};
+        makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "bar123");
+        const auto [hasMatched, transformedEvent] =
+                matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+        EXPECT_TRUE(hasMatched);
+        ASSERT_NE(transformedEvent, nullptr);
+        const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+        ASSERT_EQ(fieldValues.size(), 7);
+        EXPECT_EQ(fieldValues[0].mValue.int_value, 1111);
+        EXPECT_EQ(fieldValues[1].mValue.str_value, "foo");
+        EXPECT_EQ(fieldValues[2].mValue.int_value, 2222);
+        EXPECT_EQ(fieldValues[3].mValue.str_value, "bar");
+        EXPECT_EQ(fieldValues[4].mValue.int_value, 3333);
+        EXPECT_EQ(fieldValues[5].mValue.str_value, "foo");
+        EXPECT_EQ(fieldValues[6].mValue.str_value, "bar123");
+    }
+}
+
+TEST(AtomMatcherTest, TestStringReplaceAttributionTagAnyAndRootOnMatchedFields) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the matcher. Replace all attribution tags but match on any tag.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* attributionFvm =
+            matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    attributionFvm->set_field(FIELD_ID_1);
+    attributionFvm->set_position(Position::ANY);
+    FieldValueMatcher* attributionTagFvm =
+            attributionFvm->mutable_matches_tuple()->add_field_value_matcher();
+    attributionTagFvm->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    attributionTagFvm->set_eq_string("bar");
+    StringReplacer* stringReplacer = attributionTagFvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+    FieldValueMatcher* rootFvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    rootFvm->set_field(FIELD_ID_2);
+    rootFvm->set_eq_string("blah");
+    stringReplacer = rootFvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, {1111, 2222, 3333} /* uids */,
+                                {"location1", "location2", "location3"} /* tags */,
+                                "some value123" /* name */);
+
+        EXPECT_FALSE(matchesSimple(uidMap, matcher.simple_atom_matcher(), event).matched);
+    }
+
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, {1111, 2222, 3333} /* uids */,
+                                {"foo1", "bar2", "foo3"} /* tags */, "bar123" /* name */);
+        EXPECT_FALSE(matchesSimple(uidMap, matcher.simple_atom_matcher(), event).matched);
+    }
+
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, {1111, 2222, 3333} /* uids */,
+                                {"foo1", "bar2", "foo3"} /* tags */, "blah123" /* name */);
+        const auto [hasMatched, transformedEvent] =
+                matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+        EXPECT_TRUE(hasMatched);
+        ASSERT_NE(transformedEvent, nullptr);
+        const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+        ASSERT_EQ(fieldValues.size(), 7);
+        EXPECT_EQ(fieldValues[0].mValue.int_value, 1111);
+        EXPECT_EQ(fieldValues[1].mValue.str_value, "foo");
+        EXPECT_EQ(fieldValues[2].mValue.int_value, 2222);
+        EXPECT_EQ(fieldValues[3].mValue.str_value, "bar");
+        EXPECT_EQ(fieldValues[4].mValue.int_value, 3333);
+        EXPECT_EQ(fieldValues[5].mValue.str_value, "foo");
+        EXPECT_EQ(fieldValues[6].mValue.str_value, "blah");
+    }
+}
+
+TEST(AtomMatcherTest, TestStringReplaceAttributionTagAnyWithAttributionUidValueMatcher) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the matcher. Replace all attribution tags but match on any uid and tag.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* attributionFvm =
+            matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    attributionFvm->set_field(FIELD_ID_1);
+    attributionFvm->set_position(Position::ANY);
+    FieldValueMatcher* attributionUidFvm =
+            attributionFvm->mutable_matches_tuple()->add_field_value_matcher();
+    attributionUidFvm->set_field(ATTRIBUTION_UID_FIELD_ID);
+    attributionUidFvm->set_eq_int(2222);
+    FieldValueMatcher* attributionTagFvm =
+            attributionFvm->mutable_matches_tuple()->add_field_value_matcher();
+    attributionTagFvm->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    attributionTagFvm->set_eq_string("bar");
+    StringReplacer* stringReplacer = attributionTagFvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, {1111, 2222, 3333} /* uids */,
+                                {"location1", "location2", "location3"} /* tags */,
+                                "some value123" /* name */);
+
+        EXPECT_FALSE(matchesSimple(uidMap, matcher.simple_atom_matcher(), event).matched);
+    }
+
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, {1111, 3223, 3333} /* uids */,
+                                {"foo1", "bar2", "foo3"} /* tags */, "bar123" /* name */);
+        EXPECT_FALSE(matchesSimple(uidMap, matcher.simple_atom_matcher(), event).matched);
+    }
+
+    {
+        LogEvent event(/*uid=*/0, /*pid=*/0);
+        makeAttributionLogEvent(&event, TAG_ID, 0, {1111, 2222, 3333} /* uids */,
+                                {"foo1", "bar2", "foo3"} /* tags */, "bar123" /* name */);
+        const auto [hasMatched, transformedEvent] =
+                matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+        EXPECT_TRUE(hasMatched);
+        ASSERT_NE(transformedEvent, nullptr);
+        const vector<FieldValue>& fieldValues = transformedEvent->getValues();
+        ASSERT_EQ(fieldValues.size(), 7);
+        EXPECT_EQ(fieldValues[0].mValue.int_value, 1111);
+        EXPECT_EQ(fieldValues[1].mValue.str_value, "foo");
+        EXPECT_EQ(fieldValues[2].mValue.int_value, 2222);
+        EXPECT_EQ(fieldValues[3].mValue.str_value, "bar");
+        EXPECT_EQ(fieldValues[4].mValue.int_value, 3333);
+        EXPECT_EQ(fieldValues[5].mValue.str_value, "foo");
+        EXPECT_EQ(fieldValues[6].mValue.str_value, "bar123");
+    }
+}
+
+TEST(AtomMatcherTest, TestStringReplaceBadRegex) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value123");
+
+    // Set up the matcher. Replace second field.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(FIELD_ID_2);
+    StringReplacer* stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"(*[\d]+$)");  // bad regex: asterisk not preceded by any expression.
+    stringReplacer->set_replacement("");
+
+    const auto [hasMatched, transformedEvent] =
+            matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+    EXPECT_TRUE(hasMatched);
+    ASSERT_EQ(transformedEvent, nullptr);
+}
+
+TEST(AtomMatcherTest, TestStringReplaceNoop) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeAttributionLogEvent(&event, TAG_ID, 0, attributionUids, attributionTags, "some value123");
+
+    // Set up the matcher. Replace second field.
+    AtomMatcher matcher = CreateSimpleAtomMatcher("matcher", TAG_ID);
+    FieldValueMatcher* fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(FIELD_ID_2);
+    StringReplacer* stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"(this_pattern_should_not_match)");
+    stringReplacer->set_replacement("");
+
+    const auto [hasMatched, transformedEvent] =
+            matchesSimple(uidMap, matcher.simple_atom_matcher(), event);
+    EXPECT_TRUE(hasMatched);
+    ASSERT_EQ(transformedEvent, nullptr);
 }
 
 #else
diff --git a/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index 6908f57..a6d50d9 100644
--- a/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -466,7 +466,6 @@
 
 TEST(DurationMetricE2eTest, TestWithSlicedCondition) {
     StatsdConfig config;
-    auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
diff --git a/statsd/tests/e2e/StringReplace_e2e_test.cpp b/statsd/tests/e2e/StringReplace_e2e_test.cpp
new file mode 100644
index 0000000..27bcf52
--- /dev/null
+++ b/statsd/tests/e2e/StringReplace_e2e_test.cpp
@@ -0,0 +1,695 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <gtest_matchers.h>
+
+#include "src/StatsLogProcessor.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+
+const int64_t metricId = 123456;
+const int TEST_ATOM_REPORTED_STRING_FIELD_ID = 5;
+const int SUBSYSTEM_SLEEP_STATE_SUBSYSTEM_NAME_FIELD_ID = 1;
+const int SUBSYSTEM_SLEEP_STATE_SUBNAME_FIELD_ID = 2;
+const int SUBSYSTEM_SLEEP_STATE_TIME_MILLIS_FIELD_ID = 4;
+const int SCHEDULED_JOB_STATE_CHANGED_JOB_NAME_FIELD_ID = 2;
+const int ACTIVITY_FOREGROUND_STATE_CHANGED_UID_FIELD_ID = 1;
+const int ACTIVITY_FOREGROUND_STATE_CHANGED_PKG_NAME_FIELD_ID = 2;
+const int WAKELOCK_STATE_CHANGED_TAG_FIELD_ID = 3;
+const int ATTRIBUTION_CHAIN_FIELD_ID = 1;
+const int ATTRIBUTION_TAG_FIELD_ID = 2;
+
+std::unique_ptr<LogEvent> CreateTestAtomReportedEventStringDim(uint64_t timestampNs,
+                                                               const string& stringField) {
+    return CreateTestAtomReportedEventWithPrimitives(
+            timestampNs, 0 /* intField */, 0l /* longField */, 0.0f /* floatField */, stringField,
+            false /* boolField */, TestAtomReported::OFF /* enumField */);
+}
+
+StatsdConfig CreateStatsdConfig() {
+    StatsdConfig config;
+    config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
+    config.set_hash_strings_in_metric_report(false);
+
+    return config;
+}
+
+}  // namespace
+
+TEST(StringReplaceE2eTest, TestPushedDimension) {
+    StatsdConfig config = CreateStatsdConfig();
+
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("TestAtomMatcher", util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = config.mutable_atom_matcher(0)
+                                     ->mutable_simple_atom_matcher()
+                                     ->add_field_value_matcher();
+    fvm->set_field(TEST_ATOM_REPORTED_STRING_FIELD_ID);
+    StringReplacer* stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    CountMetric* countMetric = config.add_count_metric();
+    *countMetric = createCountMetric("TestCountMetric", config.atom_matcher(0).id() /* what */,
+                                     nullopt /* condition */, {} /* states */);
+    countMetric->mutable_dimensions_in_what()->set_field(util::TEST_ATOM_REPORTED);
+    countMetric->mutable_dimensions_in_what()->add_child()->set_field(
+            TEST_ATOM_REPORTED_STRING_FIELD_ID);
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    const int uid = 12345;
+    const int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            bucketStartTimeNs, bucketStartTimeNs, config, cfgKey, nullptr, 0, new UidMap());
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 20 * NS_PER_SEC,
+                                                          "dimA" /* stringField */));  // 0:30
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 40 * NS_PER_SEC,
+                                                          "dimA123" /* stringField */));  // 0:50
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 60 * NS_PER_SEC,
+                                                          "dimA123B" /* stringField */));  // 1:10
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 80 * NS_PER_SEC,
+                                                          "dimC0" /* stringField */));  // 1:20
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 90 * NS_PER_SEC,
+                                                          "dimC00000" /* stringField */));  // 1:30
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 100 * NS_PER_SEC,
+                                                          "dimC" /* stringField */));  // 1:40
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(3, countMetrics.data_size());
+
+    CountMetricData data = countMetrics.data(0);
+    DimensionsValue dimValue = data.dimensions_in_what();
+    EXPECT_EQ(dimValue.field(), util::TEST_ATOM_REPORTED);
+    ASSERT_EQ(dimValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimValue.value_tuple().dimensions_value(0).field(),
+              TEST_ATOM_REPORTED_STRING_FIELD_ID);
+    EXPECT_EQ(dimValue.value_tuple().dimensions_value(0).value_str(), "dimA");
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(2, data.bucket_info(0).count());
+
+    data = countMetrics.data(1);
+    dimValue = data.dimensions_in_what();
+    EXPECT_EQ(dimValue.field(), util::TEST_ATOM_REPORTED);
+    ASSERT_EQ(dimValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimValue.value_tuple().dimensions_value(0).field(),
+              TEST_ATOM_REPORTED_STRING_FIELD_ID);
+    EXPECT_EQ(dimValue.value_tuple().dimensions_value(0).value_str(), "dimA123B");
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+
+    data = countMetrics.data(2);
+    dimValue = data.dimensions_in_what();
+    EXPECT_EQ(dimValue.field(), util::TEST_ATOM_REPORTED);
+    ASSERT_EQ(dimValue.value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(dimValue.value_tuple().dimensions_value(0).field(),
+              TEST_ATOM_REPORTED_STRING_FIELD_ID);
+    EXPECT_EQ(dimValue.value_tuple().dimensions_value(0).value_str(), "dimC");
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(3, data.bucket_info(0).count());
+}
+
+TEST(StringReplaceE2eTest, TestPushedWhat) {
+    StatsdConfig config = CreateStatsdConfig();
+
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("TestAtomMatcher", util::TEST_ATOM_REPORTED);
+
+    FieldValueMatcher* fvm = config.mutable_atom_matcher(0)
+                                     ->mutable_simple_atom_matcher()
+                                     ->add_field_value_matcher();
+    fvm->set_field(TEST_ATOM_REPORTED_STRING_FIELD_ID);
+    StringReplacer* stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    *config.add_gauge_metric() = createGaugeMetric(
+            "TestAtomGaugeMetric", config.atom_matcher(0).id() /* what */,
+            GaugeMetric::FIRST_N_SAMPLES, nullopt /* condition */, nullopt /* triggerEvent */);
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000LL;
+    const int uid = 12345;
+    const int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            bucketStartTimeNs, bucketStartTimeNs, config, cfgKey, nullptr, 0, new UidMap());
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 20 * NS_PER_SEC,
+                                                          "dimA" /* stringField */));  // 0:30
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 40 * NS_PER_SEC,
+                                                          "dimA123" /* stringField */));  // 0:50
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 60 * NS_PER_SEC,
+                                                          "dimA123B" /* stringField */));  // 1:10
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 80 * NS_PER_SEC,
+                                                          "dimC0" /* stringField */));  // 1:20
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 90 * NS_PER_SEC,
+                                                          "dimC00000" /* stringField */));  // 1:30
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 100 * NS_PER_SEC,
+                                                          "dimC" /* stringField */));  // 1:40
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(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());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_gauge_metrics());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ(gaugeMetrics.data_size(), 1);
+
+    auto data = gaugeMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+
+    ASSERT_EQ(6, data.bucket_info(0).atom_size());
+    EXPECT_EQ(data.bucket_info(0).atom(0).test_atom_reported().string_field(), "dimA");
+    EXPECT_EQ(data.bucket_info(0).atom(1).test_atom_reported().string_field(), "dimA");
+    EXPECT_EQ(data.bucket_info(0).atom(2).test_atom_reported().string_field(), "dimA123B");
+    EXPECT_EQ(data.bucket_info(0).atom(3).test_atom_reported().string_field(), "dimC");
+    EXPECT_EQ(data.bucket_info(0).atom(4).test_atom_reported().string_field(), "dimC");
+    EXPECT_EQ(data.bucket_info(0).atom(5).test_atom_reported().string_field(), "dimC");
+}
+
+TEST(StringReplaceE2eTest, TestPulledDimension) {
+    StatsdConfig config = CreateStatsdConfig();
+
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemMatcher", util::SUBSYSTEM_SLEEP_STATE);
+    FieldValueMatcher* fvm = config.mutable_atom_matcher(0)
+                                     ->mutable_simple_atom_matcher()
+                                     ->add_field_value_matcher();
+    fvm->set_field(SUBSYSTEM_SLEEP_STATE_SUBSYSTEM_NAME_FIELD_ID);
+    StringReplacer* stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+    stringReplacer->set_replacement("");
+
+    *config.add_gauge_metric() = createGaugeMetric(
+            "SubsystemGaugeMetric", config.atom_matcher(0).id() /* what */,
+            GaugeMetric::RANDOM_ONE_SAMPLE, nullopt /* condition */, nullopt /* triggerEvent */);
+    *config.mutable_gauge_metric(0)->mutable_dimensions_in_what() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {1 /* subsystem name */});
+
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+    int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             util::SUBSYSTEM_SLEEP_STATE);
+    processor->mPullerManager->ForceClearPullerCache();
+
+    // Pulling alarm arrives on time and reset the sequential pulling alarm.
+    processor->informPullAlarmFired(baseTimeNs + 2 * bucketSizeNs + 1);
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 3 * bucketSizeNs + 10, false, true,
+                            ADB_DUMP, FAST, &buffer);
+    EXPECT_GT(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(gaugeMetrics.data_size(), 1);
+
+    auto data = gaugeMetrics.data(0);
+    EXPECT_EQ(util::SUBSYSTEM_SLEEP_STATE, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(SUBSYSTEM_SLEEP_STATE_SUBSYSTEM_NAME_FIELD_ID,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+
+    // Trailing numbers are trimmed from the dimension: subsystem_name_# --> subsystem_name_
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_str(),
+              "subsystem_name_");
+}
+
+TEST(StringReplaceE2eTest, TestPulledWhat) {
+    StatsdConfig config = CreateStatsdConfig();
+
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemMatcher", util::SUBSYSTEM_SLEEP_STATE);
+    FieldValueMatcher* fvm = config.mutable_atom_matcher(0)
+                                     ->mutable_simple_atom_matcher()
+                                     ->add_field_value_matcher();
+    fvm->set_field(SUBSYSTEM_SLEEP_STATE_SUBNAME_FIELD_ID);
+    StringReplacer* stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"(foo)");
+    stringReplacer->set_replacement("bar");
+
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+    *config.add_predicate() = CreateScreenIsOffPredicate();
+
+    *config.add_gauge_metric() =
+            createGaugeMetric("SubsystemGaugeMetric", config.atom_matcher(0).id() /* what */,
+                              GaugeMetric::RANDOM_ONE_SAMPLE,
+                              config.predicate(0).id() /* condition */, nullopt /* triggerEvent */);
+
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+    int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             util::SUBSYSTEM_SLEEP_STATE);
+    processor->mPullerManager->ForceClearPullerCache();
+
+    auto screenOffEvent =
+            CreateScreenStateChangedEvent(configAddedTimeNs + 55, android::view::DISPLAY_STATE_OFF);
+    processor->OnLogEvent(screenOffEvent.get());
+
+    // Pulling alarm arrives on time and reset the sequential pulling alarm.
+    processor->informPullAlarmFired(baseTimeNs + 2 * bucketSizeNs + 1);
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 3 * bucketSizeNs + 10, false, true,
+                            ADB_DUMP, FAST, &buffer);
+    EXPECT_GT(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(gaugeMetrics.data_size(), 1);
+
+    auto data = gaugeMetrics.data(0);
+    ASSERT_EQ(2, data.bucket_info_size());
+
+    ASSERT_EQ(1, data.bucket_info(0).atom_size());
+    EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().subname(),
+              "subsystem_subname bar");
+
+    ASSERT_EQ(1, data.bucket_info(1).atom_size());
+    EXPECT_EQ(data.bucket_info(1).atom(0).subsystem_sleep_state().subname(),
+              "subsystem_subname bar");
+}
+
+TEST(StringReplaceE2eTest, TestCondition) {
+    StatsdConfig config = CreateStatsdConfig();
+
+    AtomMatcher matcher = CreateStartScheduledJobAtomMatcher();
+    FieldValueMatcher* fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(SCHEDULED_JOB_STATE_CHANGED_JOB_NAME_FIELD_ID);
+    fvm->set_eq_string("foo");
+    StringReplacer* stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"(com.google.)");
+    stringReplacer->set_replacement("");
+    *config.add_atom_matcher() = matcher;
+    matcher = CreateFinishScheduledJobAtomMatcher();
+    fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(SCHEDULED_JOB_STATE_CHANGED_JOB_NAME_FIELD_ID);
+    fvm->set_eq_string("foo");
+    stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"(com.google.)");
+    stringReplacer->set_replacement("");
+    *config.add_atom_matcher() = matcher;
+
+    Predicate predicate = CreateScheduledJobPredicate();
+    *config.add_predicate() = predicate;
+
+    matcher = CreateSimpleAtomMatcher("TestAtomMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = matcher;
+
+    CountMetric* countMetric = config.add_count_metric();
+    *countMetric = createCountMetric("TestCountMetric", matcher.id() /* what */,
+                                     predicate.id() /* condition */, {} /* states */);
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(countMetric->bucket()) * 1000000LL;
+    const int uid = 12345;
+    const int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            bucketStartTimeNs, bucketStartTimeNs, config, cfgKey, nullptr, 0, new UidMap());
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 20 * NS_PER_SEC,
+                                                  {1001} /* uids */, {"App1"},
+                                                  "com.google.job1"));  // 0:30
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 30 * NS_PER_SEC,
+                                                          "str" /* stringField */));  // 0:40
+    events.push_back(CreateStartScheduledJobEvent(bucketStartTimeNs + 40 * NS_PER_SEC,
+                                                  {1002} /* uids */, {"App1"},
+                                                  "com.google.foo"));  // 0:50
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 50 * NS_PER_SEC,
+                                                          "str" /* stringField */));  // 1:00
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 60 * NS_PER_SEC,
+                                                          "str" /* stringField */));  // 1:10
+    events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 70 * NS_PER_SEC,
+                                                   {1001} /* uids */, {"App1"},
+                                                   "com.google.job1"));  // 1:20
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 80 * NS_PER_SEC,
+                                                          "str" /* stringField */));  // 1:30
+    events.push_back(CreateFinishScheduledJobEvent(bucketStartTimeNs + 90 * NS_PER_SEC,
+                                                   {1002} /* uids */, {"App1"},
+                                                   "com.google.foo"));  // 1:40
+    events.push_back(CreateTestAtomReportedEventStringDim(bucketStartTimeNs + 100 * NS_PER_SEC,
+                                                          "str" /* stringField */));  // 1:50
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(1, countMetrics.data_size());
+
+    CountMetricData data = countMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(3, data.bucket_info(0).count());
+}
+
+TEST(StringReplaceE2eTest, TestDurationMetric) {
+    StatsdConfig config = CreateStatsdConfig();
+
+    AtomMatcher matcher = CreateAcquireWakelockAtomMatcher();
+    FieldValueMatcher* fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(ATTRIBUTION_CHAIN_FIELD_ID);
+    fvm->set_position(Position::FIRST);
+    fvm->mutable_matches_tuple()->add_field_value_matcher()->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    StringReplacer* stringReplacer =
+            fvm->mutable_matches_tuple()->mutable_field_value_matcher(0)->mutable_replace_string();
+    stringReplacer->set_regex(R"([0-9]+)");
+    stringReplacer->set_replacement("#");
+    *config.add_atom_matcher() = matcher;
+
+    matcher = CreateReleaseWakelockAtomMatcher();
+    fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(ATTRIBUTION_CHAIN_FIELD_ID);
+    fvm->set_position(Position::FIRST);
+    fvm->mutable_matches_tuple()->add_field_value_matcher()->set_field(ATTRIBUTION_TAG_FIELD_ID);
+    stringReplacer =
+            fvm->mutable_matches_tuple()->mutable_field_value_matcher(0)->mutable_replace_string();
+    stringReplacer->set_regex(R"([0-9]+)");
+    stringReplacer->set_replacement("#");
+    *config.add_atom_matcher() = matcher;
+
+    matcher = CreateMoveToBackgroundAtomMatcher();
+    fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(ACTIVITY_FOREGROUND_STATE_CHANGED_PKG_NAME_FIELD_ID);
+    stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([0-9]+)");
+    stringReplacer->set_replacement("#");
+    *config.add_atom_matcher() = matcher;
+
+    matcher = CreateMoveToForegroundAtomMatcher();
+    fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(ACTIVITY_FOREGROUND_STATE_CHANGED_PKG_NAME_FIELD_ID);
+    stringReplacer = fvm->mutable_replace_string();
+    stringReplacer->set_regex(R"([0-9]+)");
+    stringReplacer->set_replacement("#");
+    *config.add_atom_matcher() = matcher;
+
+    Predicate holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+    // The predicate is dimensioning by first attribution node by uid and tag.
+    *holdingWakelockPredicate.mutable_simple_predicate()->mutable_dimensions() =
+            CreateAttributionUidAndTagDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    *config.add_predicate() = holdingWakelockPredicate;
+
+    Predicate isInBackgroundPredicate = CreateIsInBackgroundPredicate();
+    *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
+            CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED,
+                             {ACTIVITY_FOREGROUND_STATE_CHANGED_UID_FIELD_ID,
+                              ACTIVITY_FOREGROUND_STATE_CHANGED_PKG_NAME_FIELD_ID});
+    *config.add_predicate() = isInBackgroundPredicate;
+
+    DurationMetric durationMetric =
+            createDurationMetric("WakelockDuration", holdingWakelockPredicate.id() /* what */,
+                                 isInBackgroundPredicate.id() /* condition */, {} /* states */);
+    *durationMetric.mutable_dimensions_in_what() =
+            CreateAttributionUidAndTagDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    durationMetric.set_bucket(FIVE_MINUTES);
+    *config.add_duration_metric() = durationMetric;
+
+    // Links between wakelock state atom and condition of app is in background.
+    MetricConditionLink* link = durationMetric.add_links();
+    link->set_condition(isInBackgroundPredicate.id());
+    *link->mutable_fields_in_what() =
+            CreateAttributionUidAndTagDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    *link->mutable_fields_in_condition() =
+            CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED,
+                             {ACTIVITY_FOREGROUND_STATE_CHANGED_UID_FIELD_ID,
+                              ACTIVITY_FOREGROUND_STATE_CHANGED_PKG_NAME_FIELD_ID});
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(durationMetric.bucket()) * 1000000LL;
+    const int uid = 12345;
+    const int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            bucketStartTimeNs, bucketStartTimeNs, config, cfgKey, nullptr, 0, new UidMap());
+
+    int appUid = 123;
+    std::vector<int> attributionUids1 = {appUid};
+    std::vector<string> attributionTags1 = {"App1"};
+    std::vector<string> attributionTags2 = {"App2"};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 10 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wl1"));  // 0:10
+    events.push_back(CreateActivityForegroundStateChangedEvent(
+            bucketStartTimeNs + 22 * NS_PER_SEC, appUid, "App1", "class_name",
+            ActivityForegroundStateChanged::BACKGROUND));  // 0:22
+    events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 60 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wl1"));  // 1:00
+    events.push_back(CreateActivityForegroundStateChangedEvent(
+            bucketStartTimeNs + (3 * 60 + 15) * NS_PER_SEC, appUid, "App1", "class_name",
+            ActivityForegroundStateChanged::FOREGROUND));  // 3:15
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + (3 * 60 + 20) * NS_PER_SEC,
+                                                attributionUids1, attributionTags2,
+                                                "wl2"));  // 3:20
+    events.push_back(CreateActivityForegroundStateChangedEvent(
+            bucketStartTimeNs + (3 * 60 + 30) * NS_PER_SEC, appUid, "App1", "class_name",
+            ActivityForegroundStateChanged::BACKGROUND));  // 3:30
+    events.push_back(CreateActivityForegroundStateChangedEvent(
+            bucketStartTimeNs + (3 * 60 + 40) * NS_PER_SEC, appUid, "App2", "class_name",
+            ActivityForegroundStateChanged::FOREGROUND));  // 3:40
+    events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + (3 * 60 + 50) * NS_PER_SEC,
+                                                attributionUids1, attributionTags2,
+                                                "wl2"));  // 3:50
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    ASSERT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(1, durationMetrics.data_size());
+
+    DurationMetricData data = durationMetrics.data(0);
+    // Validate dimension value.
+    ValidateAttributionUidAndTagDimension(data.dimensions_in_what(), util::WAKELOCK_STATE_CHANGED,
+                                          appUid, "App#");
+    // Validate bucket info.
+    ASSERT_EQ(1, data.bucket_info_size());
+
+    auto bucketInfo = data.bucket_info(0);
+    EXPECT_EQ(bucketStartTimeNs, bucketInfo.start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.end_bucket_elapsed_nanos());
+    EXPECT_EQ(48 * NS_PER_SEC, bucketInfo.duration_nanos());
+}
+
+TEST(StringReplaceE2eTest, TestMultipleMatchersForAtom) {
+    StatsdConfig config = CreateStatsdConfig();
+
+    {
+        AtomMatcher matcher = CreateSimpleAtomMatcher("Matcher1", util::SUBSYSTEM_SLEEP_STATE);
+        FieldValueMatcher* fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+        fvm->set_field(SUBSYSTEM_SLEEP_STATE_SUBSYSTEM_NAME_FIELD_ID);
+        fvm->set_eq_string("subsystem_name_1");
+        fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+        fvm->set_field(SUBSYSTEM_SLEEP_STATE_SUBNAME_FIELD_ID);
+        fvm->set_eq_string("subsystem_subname bar");
+        StringReplacer* stringReplacer = fvm->mutable_replace_string();
+        stringReplacer->set_regex(R"(foo)");
+        stringReplacer->set_replacement("bar");
+        *config.add_atom_matcher() = matcher;
+
+        *config.add_value_metric() =
+                createValueMetric("Value1", matcher, SUBSYSTEM_SLEEP_STATE_TIME_MILLIS_FIELD_ID,
+                                  nullopt /* condition */, {} /* states */);
+    }
+    {
+        AtomMatcher matcher = CreateSimpleAtomMatcher("Matcher2", util::SUBSYSTEM_SLEEP_STATE);
+        FieldValueMatcher* fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+        fvm->set_field(SUBSYSTEM_SLEEP_STATE_SUBSYSTEM_NAME_FIELD_ID);
+        fvm->set_eq_string("subsystem_name_2");
+        fvm = matcher.mutable_simple_atom_matcher()->add_field_value_matcher();
+        fvm->set_field(SUBSYSTEM_SLEEP_STATE_SUBNAME_FIELD_ID);
+        fvm->set_eq_string("subsystem_subname foo");
+        *config.add_atom_matcher() = matcher;
+
+        *config.add_value_metric() =
+                createValueMetric("Value2", matcher, SUBSYSTEM_SLEEP_STATE_TIME_MILLIS_FIELD_ID,
+                                  nullopt /* condition */, {} /* states */);
+    }
+
+    int64_t baseTimeNs = getElapsedRealtimeNs();
+    int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+                                             SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                             util::SUBSYSTEM_SLEEP_STATE);
+    processor->mPullerManager->ForceClearPullerCache();
+
+    // Pulling alarm arrives on time and reset the sequential pulling alarm.
+    processor->informPullAlarmFired(baseTimeNs + 2 * bucketSizeNs + 1);
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 3 * bucketSizeNs + 10, false, true,
+                            ADB_DUMP, FAST, &buffer);
+    EXPECT_GT(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(2, reports.reports(0).metrics_size());
+
+    {
+        StatsLogReport::ValueMetricDataWrapper valueMetrics;
+        sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(),
+                                        &valueMetrics);
+        ASSERT_EQ(valueMetrics.data_size(), 1);
+
+        ValueMetricData data = valueMetrics.data(0);
+        ASSERT_EQ(data.bucket_info_size(), 1);
+        ASSERT_EQ(data.bucket_info(0).values_size(), 1);
+    }
+    {
+        StatsLogReport::ValueMetricDataWrapper valueMetrics;
+        sortMetricDataByDimensionsValue(reports.reports(0).metrics(1).value_metrics(),
+                                        &valueMetrics);
+        ASSERT_EQ(valueMetrics.data_size(), 1);
+
+        ValueMetricData data = valueMetrics.data(0);
+        ASSERT_EQ(data.bucket_info_size(), 1);
+        ASSERT_EQ(data.bucket_info(0).values_size(), 1);
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
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 7b12a36..21ddb26 100644
--- a/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
+++ b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -1985,6 +1985,313 @@
     EXPECT_EQ(kllProducer->mDimensionHardLimit, actualLimit);
 }
 
+TEST_F(MetricsManagerUtilTest, TestMissingValueMatcherAndStringReplacer) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(SCREEN_STATE_ATOM_ID);
+    matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_NE(actualInvalidConfigReason, nullopt);
+    EXPECT_EQ(actualInvalidConfigReason->reason,
+              INVALID_CONFIG_REASON_MATCHER_NO_VALUE_MATCHER_NOR_STRING_REPLACER);
+    EXPECT_THAT(actualInvalidConfigReason->matcherIds, ElementsAre(111));
+}
+
+TEST_F(MetricsManagerUtilTest, TestMatcherWithValueMatcherOnly) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(SCREEN_STATE_ATOM_ID);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(2 /*int_field*/);
+    fvm->set_eq_int(1);
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_EQ(actualInvalidConfigReason, nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestMatcherWithStringReplacerOnly) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(SCREEN_STATE_ATOM_ID);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(5 /*string_field*/);
+    fvm->mutable_replace_string()->set_regex(R"([\d]+$)");
+    fvm->mutable_replace_string()->set_replacement("#");
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_EQ(actualInvalidConfigReason, nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestValueMatcherWithPositionAll) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(9 /*repeated_int_field*/);
+    fvm->set_position(Position::ALL);
+    fvm->set_eq_int(1);
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_NE(actualInvalidConfigReason, nullopt);
+    EXPECT_EQ(actualInvalidConfigReason->reason,
+              INVALID_CONFIG_REASON_MATCHER_VALUE_MATCHER_WITH_POSITION_ALL);
+    EXPECT_THAT(actualInvalidConfigReason->matcherIds, ElementsAre(111));
+}
+
+TEST_F(MetricsManagerUtilTest, TestValueMatcherAndStringReplaceWithPositionAll) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(12 /*repeated_string_field*/);
+    fvm->set_position(Position::ALL);
+    fvm->set_eq_string("foo");
+    fvm->mutable_replace_string()->set_regex(R"([\d]+$)");
+    fvm->mutable_replace_string()->set_replacement("");
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_NE(actualInvalidConfigReason, nullopt);
+    EXPECT_EQ(actualInvalidConfigReason->reason,
+              INVALID_CONFIG_REASON_MATCHER_VALUE_MATCHER_WITH_POSITION_ALL);
+    EXPECT_THAT(actualInvalidConfigReason->matcherIds, ElementsAre(111));
+}
+
+TEST_F(MetricsManagerUtilTest, TestValueMatcherWithPositionAllNested) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    // Match on attribution_node[ALL].uid = 1
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(1 /*attribution_node*/);
+    fvm->set_position(Position::ALL);
+    fvm->mutable_matches_tuple()->add_field_value_matcher()->set_field(1 /* uid */);
+    fvm->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_int(1);
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_NE(actualInvalidConfigReason, nullopt);
+    EXPECT_EQ(actualInvalidConfigReason->reason,
+              INVALID_CONFIG_REASON_MATCHER_VALUE_MATCHER_WITH_POSITION_ALL);
+    EXPECT_THAT(actualInvalidConfigReason->matcherIds, ElementsAre(111));
+}
+
+TEST_F(MetricsManagerUtilTest, TestValueMatcherAndStringReplaceWithPositionAllNested) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    // Match on attribution_node[ALL].uid = 1
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(1 /*attribution_node*/);
+    fvm->set_position(Position::ALL);
+    fvm->mutable_matches_tuple()->add_field_value_matcher()->set_field(2 /* tag */);
+    fvm->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("foo");
+    fvm->mutable_matches_tuple()
+            ->mutable_field_value_matcher(0)
+            ->mutable_replace_string()
+            ->set_regex(R"([\d]+$)");
+    fvm->mutable_matches_tuple()
+            ->mutable_field_value_matcher(0)
+            ->mutable_replace_string()
+            ->set_replacement("");
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_NE(actualInvalidConfigReason, nullopt);
+    EXPECT_EQ(actualInvalidConfigReason->reason,
+              INVALID_CONFIG_REASON_MATCHER_VALUE_MATCHER_WITH_POSITION_ALL);
+    EXPECT_THAT(actualInvalidConfigReason->matcherIds, ElementsAre(111));
+}
+
+TEST_F(MetricsManagerUtilTest, TestStringReplaceWithNoValueMatcherWithPositionAny) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(12 /*repeated_string_field*/);
+    fvm->set_position(Position::ANY);
+    fvm->mutable_replace_string()->set_regex(R"([\d]+$)");
+    fvm->mutable_replace_string()->set_replacement("");
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_NE(actualInvalidConfigReason, nullopt);
+    EXPECT_EQ(actualInvalidConfigReason->reason,
+              INVALID_CONFIG_REASON_MATCHER_STRING_REPLACE_WITH_NO_VALUE_MATCHER_WITH_POSITION_ANY);
+    EXPECT_THAT(actualInvalidConfigReason->matcherIds, ElementsAre(111));
+}
+
+TEST_F(MetricsManagerUtilTest, TestStringReplaceWithNoValueMatcherWithPositionAnyNested) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    // Match on attribution_node[ALL].uid = 1
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(1 /*attribution_node*/);
+    fvm->set_position(Position::ANY);
+    fvm->mutable_matches_tuple()->add_field_value_matcher()->set_field(2 /* tag */);
+    fvm->mutable_matches_tuple()
+            ->mutable_field_value_matcher(0)
+            ->mutable_replace_string()
+            ->set_regex(R"([\d]+$)");
+    fvm->mutable_matches_tuple()
+            ->mutable_field_value_matcher(0)
+            ->mutable_replace_string()
+            ->set_replacement("");
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_NE(actualInvalidConfigReason, nullopt);
+    EXPECT_EQ(actualInvalidConfigReason->reason,
+              INVALID_CONFIG_REASON_MATCHER_STRING_REPLACE_WITH_NO_VALUE_MATCHER_WITH_POSITION_ANY);
+    EXPECT_THAT(actualInvalidConfigReason->matcherIds, ElementsAre(111));
+}
+
+TEST_F(MetricsManagerUtilTest, TestStringReplaceWithValueMatcherWithPositionAny) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(12 /*repeated_string_field*/);
+    fvm->set_position(Position::ANY);
+    fvm->set_eq_string("bar");
+    fvm->mutable_replace_string()->set_regex(R"([\d]+$)");
+    fvm->mutable_replace_string()->set_replacement("");
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_EQ(actualInvalidConfigReason, nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestStringReplaceWithValueMatcherWithPositionAnyNested) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    // Match on attribution_node[ALL].uid = 1
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(1 /*attribution_node*/);
+    fvm->set_position(Position::ANY);
+    fvm->mutable_matches_tuple()->add_field_value_matcher()->set_field(2 /* tag */);
+    fvm->mutable_matches_tuple()->mutable_field_value_matcher(0)->set_eq_string("bar");
+    fvm->mutable_matches_tuple()
+            ->mutable_field_value_matcher(0)
+            ->mutable_replace_string()
+            ->set_regex(R"([\d]+$)");
+    fvm->mutable_matches_tuple()
+            ->mutable_field_value_matcher(0)
+            ->mutable_replace_string()
+            ->set_replacement("");
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_EQ(actualInvalidConfigReason, nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestStringReplaceWithPositionAllNested) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    // Replace attribution_node[ALL].tag using "[\d]+$" -> "".
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(1 /*attribution_node*/);
+    fvm->set_position(Position::ALL);
+    fvm = fvm->mutable_matches_tuple()->add_field_value_matcher();
+    fvm->set_field(2 /* tag */);
+    fvm->mutable_replace_string()->set_regex(R"([\d]+$)");
+    fvm->mutable_replace_string()->set_replacement("");
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_EQ(actualInvalidConfigReason, nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestMatcherWithStringReplaceAndNonStringValueMatcher) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(2 /*int_field*/);
+    fvm->set_eq_int(1);
+    fvm->mutable_replace_string()->set_regex(R"([\d]+$)");
+    fvm->mutable_replace_string()->set_replacement("#");
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_NE(actualInvalidConfigReason, nullopt);
+    EXPECT_EQ(actualInvalidConfigReason->reason,
+              INVALID_CONFIG_REASON_MATCHER_INVALID_VALUE_MATCHER_WITH_STRING_REPLACE);
+    EXPECT_THAT(actualInvalidConfigReason->matcherIds, ElementsAre(111));
+}
+
+TEST_F(MetricsManagerUtilTest, TestCombinationMatcherWithStringReplace) {
+    StatsdConfig config;
+    config.set_id(12345);
+
+    AtomMatcher* matcher = config.add_atom_matcher();
+    matcher->set_id(111);
+    matcher->mutable_simple_atom_matcher()->set_atom_id(util::TEST_ATOM_REPORTED);
+    FieldValueMatcher* fvm = matcher->mutable_simple_atom_matcher()->add_field_value_matcher();
+    fvm->set_field(5 /*string_field*/);
+    fvm->mutable_replace_string()->set_regex(R"([\d]+$)");
+    fvm->mutable_replace_string()->set_replacement("#");
+
+    matcher = config.add_atom_matcher();
+    matcher->set_id(222);
+    matcher->mutable_combination()->set_operation(LogicalOperation::NOT);
+    matcher->mutable_combination()->add_matcher(111);
+
+    optional<InvalidConfigReason> actualInvalidConfigReason = initConfig(config);
+
+    ASSERT_NE(actualInvalidConfigReason, nullopt);
+    EXPECT_EQ(actualInvalidConfigReason->reason,
+              INVALID_CONFIG_REASON_MATCHER_COMBINATION_WITH_STRING_REPLACE);
+    EXPECT_THAT(actualInvalidConfigReason->matcherIds, ElementsAre(222));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/shell/ShellSubscriber_test.cpp b/statsd/tests/shell/ShellSubscriber_test.cpp
index a06f76f..4bd032e 100644
--- a/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -222,7 +222,7 @@
     TestAtomReported t;
     auto* attributionNode = t.add_attribution_node();
     attributionNode->set_uid(1001);
-    attributionNode->set_tag("app1");
+    attributionNode->set_tag("app");  // String transformation removes trailing digits.
     t.set_int_field(intFieldValue);
     t.set_long_field(0);
     t.set_float_field(0.0f);
@@ -252,6 +252,15 @@
 
         ShellSubscription config;
         config.add_pushed()->set_atom_id(TEST_ATOM_REPORTED);
+        FieldValueMatcher* fvm = config.mutable_pushed(0)->add_field_value_matcher();
+        fvm->set_field(1);  // attribution_chain
+        fvm->set_position(Position::FIRST);
+        fvm = fvm->mutable_matches_tuple()->add_field_value_matcher();
+        fvm->set_field(2);  // tag field
+        fvm->mutable_replace_string()->set_regex(
+                R"([\d]+$)");  // match trailing digits, example "42" in "foo42".
+        fvm->mutable_replace_string()->set_replacement("");
+
         config.add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
         config.add_pushed()->set_atom_id(PHONE_SIGNAL_STRENGTH_CHANGED);
         configBytes = protoToBytes(config);
diff --git a/statsd/tests/statsd_test_util.cpp b/statsd/tests/statsd_test_util.cpp
index 084fb2f..8e80a20 100644
--- a/statsd/tests/statsd_test_util.cpp
+++ b/statsd/tests/statsd_test_util.cpp
@@ -1079,6 +1079,18 @@
                                        repeatedBoolFieldLength, repeatedEnumField);
 }
 
+std::unique_ptr<LogEvent> CreateTestAtomReportedEventWithPrimitives(
+        uint64_t timestampNs, int intField, long longField, float floatField,
+        const string& stringField, bool boolField, TestAtomReported::State enumField) {
+    return CreateTestAtomReportedEvent(
+            timestampNs, /* attributionUids */ {1001},
+            /* attributionTags */ {"app1"}, intField, longField, floatField, stringField, boolField,
+            enumField, /* bytesField */ {},
+            /* repeatedIntField */ {}, /* repeatedLongField */ {}, /* repeatedFloatField */ {},
+            /* repeatedStringField */ {}, /* repeatedBoolField */ {},
+            /* repeatedBoolFieldLength */ 0, /* repeatedEnumField */ {});
+}
+
 std::unique_ptr<LogEvent> CreateTestAtomReportedEvent(
         uint64_t timestampNs, const vector<int>& attributionUids,
         const vector<string>& attributionTags, const int intField, const long longField,
@@ -1160,14 +1172,15 @@
 }
 
 std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent(
-        uint64_t timestampNs, const int uid, const ActivityForegroundStateChanged::State state) {
+        uint64_t timestampNs, const int uid, const string& pkgName, const string& className,
+        const ActivityForegroundStateChanged::State state) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::ACTIVITY_FOREGROUND_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
     AStatsEvent_writeInt32(statsEvent, uid);
-    AStatsEvent_writeString(statsEvent, "pkg_name");
-    AStatsEvent_writeString(statsEvent, "class_name");
+    AStatsEvent_writeString(statsEvent, pkgName.c_str());
+    AStatsEvent_writeString(statsEvent, className.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
@@ -1176,12 +1189,12 @@
 }
 
 std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid) {
-    return CreateActivityForegroundStateChangedEvent(timestampNs, uid,
+    return CreateActivityForegroundStateChangedEvent(timestampNs, uid, "pkg_name", "class_name",
                                                      ActivityForegroundStateChanged::BACKGROUND);
 }
 
 std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(uint64_t timestampNs, const int uid) {
-    return CreateActivityForegroundStateChangedEvent(timestampNs, uid,
+    return CreateActivityForegroundStateChangedEvent(timestampNs, uid, "pkg_name", "class_name",
                                                      ActivityForegroundStateChanged::FOREGROUND);
 }
 
diff --git a/statsd/tests/statsd_test_util.h b/statsd/tests/statsd_test_util.h
index ca38944..43d9c7d 100644
--- a/statsd/tests/statsd_test_util.h
+++ b/statsd/tests/statsd_test_util.h
@@ -477,6 +477,10 @@
 // Create malformed log event for battery state change.
 std::unique_ptr<LogEvent> CreateMalformedBatteryStateChangedEvent(const uint64_t timestampNs);
 
+std::unique_ptr<LogEvent> CreateActivityForegroundStateChangedEvent(
+        uint64_t timestampNs, const int uid, const string& pkgName, const string& className,
+        const ActivityForegroundStateChanged::State state);
+
 // Create log event for app moving to background.
 std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid);
 
@@ -543,6 +547,10 @@
         const vector<string>& repeatedStringField, const bool* repeatedBoolField,
         const size_t repeatedBoolFieldLength, const vector<int>& repeatedEnumField);
 
+std::unique_ptr<LogEvent> CreateTestAtomReportedEventWithPrimitives(
+        uint64_t timestampNs, int intField, long longField, float floatField,
+        const string& stringField, bool boolField, TestAtomReported::State enumField);
+
 std::unique_ptr<LogEvent> CreateRestrictedLogEvent(int atomTag, int64_t timestampNs = 0);
 std::unique_ptr<LogEvent> CreateNonRestrictedLogEvent(int atomTag, int64_t timestampNs = 0);