Merge "Make StatsdStats print nicer" into main
diff --git a/.gitignore b/.gitignore
index ccff052..3c2d0cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,8 @@
 **/.idea
 **/*.iml
 **/*.ipr
+
+# VSCode
+**/.vscode/
+**/packages.modules.StatsD.code-workspace
+
diff --git a/OWNERS b/OWNERS
index fb7e5b1..09ff156 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 366902
 jeffreyhuang@google.com
 monicamwang@google.com
 muhammadq@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index fae7a94..ec71d8a 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -30,7 +30,7 @@
       "name" : "statsd_test"
     }
   ],
-  "hwasan-postsubmit" : [
+  "hwasan-presubmit" : [
     {
       "name" : "FrameworkStatsdTest"
     },
diff --git a/aidl/Android.bp b/aidl/Android.bp
index ede81df..c79f0b8 100644
--- a/aidl/Android.bp
+++ b/aidl/Android.bp
@@ -33,6 +33,7 @@
         "android/os/IPullAtomResultReceiver.aidl",
         "android/os/IStatsCompanionService.aidl",
         "android/os/IStatsd.aidl",
+        "android/os/IStatsQueryCallback.aidl",
         "android/os/StatsDimensionsValueParcel.aidl",
         "android/util/PropertyParcel.aidl",
         "android/util/StatsEventParcel.aidl",
diff --git a/aidl/android/os/IPendingIntentRef.aidl b/aidl/android/os/IPendingIntentRef.aidl
index 000a699..5a0f5d7 100644
--- a/aidl/android/os/IPendingIntentRef.aidl
+++ b/aidl/android/os/IPendingIntentRef.aidl
@@ -43,4 +43,10 @@
      oneway void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
                                          long subscriptionRuleId, in String[] cookies,
                                          in StatsDimensionsValueParcel dimensionsValueParcel);
+
+    /**
+     * Send a broadcast to the specified PendingIntent notifying it that the list of restricted
+     * metrics has changed.
+     */
+     oneway void sendRestrictedMetricsChangedBroadcast(in long[] metricIds);
 }
diff --git a/aidl/android/os/IStatsManagerService.aidl b/aidl/android/os/IStatsManagerService.aidl
index b27c2ec..f8f9122 100644
--- a/aidl/android/os/IStatsManagerService.aidl
+++ b/aidl/android/os/IStatsManagerService.aidl
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.os.IPullAtomCallback;
+import android.os.IStatsQueryCallback;
 
 /**
   * Binder interface to communicate with the Java-based statistics service helper.
@@ -106,6 +107,7 @@
      * wire-encoded of ConfigMetricsReportList.
      *
      * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     * @deprecated use #getDataFd() instead
      */
     byte[] getData(in long key, in String packageName);
 
@@ -134,6 +136,31 @@
     /** Tell StatsManagerService to unregister the puller for the given atom tag from statsd. */
     oneway void unregisterPullAtomCallback(int atomTag);
 
+    /** Section for restricted-logging methods. */
+
+    /** Queries data from underlying statsd sql store. */
+    oneway void querySql(in String sqlQuery, in int minSqlClientVersion,
+        in @nullable byte[] policyConfig, in IStatsQueryCallback queryCallback,
+        in long configKey, in String configPackage);
+
+    /**
+     * Registers the operation that is called whenever there is a change in the restricted metrics
+     * for a specified config that are present for this client. This operation allows statsd to
+     * inform the client about the current restricted metrics available to be queried for
+     * the specified config.
+     *
+     * Requires Manifest.permission.READ_RESTRICTED_STATS.
+     */
+    long[] setRestrictedMetricsChangedOperation(in PendingIntent pendingIntent, in long configKey,
+            in String configPackage);
+
+    /**
+     * Removes the restricted metrics changed operation for the specified config key/package.
+     *
+     * Requires Manifest.permission.READ_RESTRICTED_STATS.
+     */
+    void removeRestrictedMetricsChangedOperation(in long configKey, in String configPackage);
+
     /**
      * Same as #getData(in long key, in String packageName), but the data is stored in a file
      * descriptor.
diff --git a/aidl/android/os/IStatsQueryCallback.aidl b/aidl/android/os/IStatsQueryCallback.aidl
new file mode 100644
index 0000000..911c816
--- /dev/null
+++ b/aidl/android/os/IStatsQueryCallback.aidl
@@ -0,0 +1,28 @@
+/* *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+  * Binder interface to hold a Callback for Stats SQL queries.
+  * {@hide}
+  */
+interface IStatsQueryCallback {
+      oneway void sendResults(in String[] queryData, in String[] columnNames,
+        in int[] columnTypes, int rowCount);
+
+      oneway void sendFailure(String error);
+}
diff --git a/aidl/android/os/IStatsd.aidl b/aidl/android/os/IStatsd.aidl
index b9a134f..658bd6a 100644
--- a/aidl/android/os/IStatsd.aidl
+++ b/aidl/android/os/IStatsd.aidl
@@ -21,6 +21,7 @@
 import android.os.IPullAtomCallback;
 import android.os.ParcelFileDescriptor;
 import android.util.PropertyParcel;
+import android.os.IStatsQueryCallback;
 
 /**
   * Binder interface to communicate with the statistics management service.
@@ -94,6 +95,7 @@
      * wire-encoded of ConfigMetricsReportList.
      *
      * Requires Manifest.permission.DUMP.
+     * @deprecated use #getDataFd() instead for Android T+
      */
     byte[] getData(in long key, int callingUid);
 
@@ -242,6 +244,33 @@
      */
     oneway void updateProperties(in PropertyParcel[] properties);
 
+    /** Section for restricted-logging methods. */
+    /**
+     * Queries data from underlying statsd sql store.
+     */
+    oneway void querySql(in String sqlQuery, in int minSqlClientVersion,
+        in @nullable byte[] policyConfig, in IStatsQueryCallback queryCallback,
+        in long configKey, in String configPackage, in int callingUid);
+
+    /**
+     * Registers the operation that is called whenever there is a change in the restricted metrics
+     * for a specified config that are present for this client. This operation allows statsd to
+     * inform the client about the current restricted metrics available to be queried for the
+     * specified config.
+     *
+     * Requires Manifest.permission.READ_RESTRICTED_STATS
+     */
+    long[] setRestrictedMetricsChangedOperation(in long configKey, in String configPackage,
+            in IPendingIntentRef pir, int callingUid);
+
+    /**
+     * Removes the restricted metrics changed operation for the specified config package/id.
+     *
+     * Requires Manifest.permission.READ_RESTRICTED_STATS.
+     */
+    void removeRestrictedMetricsChangedOperation(in long configKey, in String configPackage,
+            in int callingUid);
+
     /** Section for atoms subscription methods. */
     /**
      * Adds a subscription for atom events.
diff --git a/apex/tests/libstatspull/Android.bp b/apex/tests/libstatspull/Android.bp
index cdac762..d3079e0 100644
--- a/apex/tests/libstatspull/Android.bp
+++ b/apex/tests/libstatspull/Android.bp
@@ -22,7 +22,7 @@
         "androidx.test.rules",
         "platformprotoslite",
         "statsdprotolite",
-        "truth-prebuilt",
+        "truth",
     ],
     libs: [
         "android.test.runner.stubs",
@@ -34,7 +34,7 @@
     srcs: [
         "src/**/*.java",
         "protos/**/*.proto",
-        ],
+    ],
     test_suites: [
         "device-tests",
         "mts",
@@ -45,7 +45,7 @@
     compile_multilib: "both",
 }
 
-cc_library_shared {
+cc_test_library {
     name: "libstatspull_testhelper",
     srcs: ["jni/stats_pull_helper.cpp"],
     cflags: [
@@ -54,12 +54,12 @@
         "-Wthread-safety",
     ],
     shared_libs: [
-        "libbinder_ndk",
-        "statsd-aidl-ndk",
+        "libstatspull",
+        "libstatssocket",
     ],
+    header_libs: ["libnativehelper_header_only"],
     static_libs: [
         "libbase",
-        "libstatspull_private",
-        "libstatssocket_private",
     ],
+    test_for: ["com.android.os.statsd"],
 }
diff --git a/framework/Android.bp b/framework/Android.bp
index d3f037d..7ffa7b5 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -21,7 +21,7 @@
     name: "statslog-statsd-java-gen",
     tools: ["stats-log-api-gen"],
     cmd: "$(location stats-log-api-gen) --java $(out) --module statsd" +
-         " --javaPackage com.android.internal.statsd --javaClass StatsdStatsLog --minApiLevel 30",
+         " --javaPackage com.android.internal.statsd --javaClass StatsdStatsLog",
     out: ["com/android/internal/statsd/StatsdStatsLog.java"],
 }
 
@@ -30,6 +30,9 @@
     srcs: [
         ":statslog-statsd-java-gen",
     ],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
     visibility: [
         "//cts/hostsidetests/statsd/apps:__subpackages__",
         "//vendor:__subpackages__",
@@ -59,6 +62,7 @@
     ],
 
     libs: [
+        "androidx.annotation_annotation",
         "framework-configinfrastructure",
     ],
 
@@ -99,15 +103,6 @@
     min_sdk_version: "30",
 }
 
-java_api_contribution {
-    name: "framework-statsd-public-stubs",
-    api_surface: "public",
-    api_file: "api/current.txt",
-    visibility: [
-        "//build/orchestrator/apis",
-    ],
-}
-
 // JNI library for StatsLog.write
 cc_library_shared {
     name: "libstats_jni",
diff --git a/framework/api/lint-baseline.txt b/framework/api/lint-baseline.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/api/lint-baseline.txt
diff --git a/framework/api/module-lib-lint-baseline.txt b/framework/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..2ae37de
--- /dev/null
+++ b/framework/api/module-lib-lint-baseline.txt
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.app.StatsManager#ACTION_STATSD_STARTED:
+    Field 'ACTION_STATSD_STARTED' is missing @BroadcastBehavior
+
+
+SdkConstant: android.app.StatsManager#ACTION_STATSD_STARTED:
+    Field 'ACTION_STATSD_STARTED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index c432a7f..41b5891 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -1,6 +1,18 @@
 // Signature format: 2.0
 package android.app {
 
+  public class StatsCursor extends android.database.AbstractCursor {
+    method @NonNull public String[] getColumnNames();
+    method public int getCount();
+    method public double getDouble(int);
+    method public float getFloat(int);
+    method public int getInt(int);
+    method public long getLong(int);
+    method public short getShort(int);
+    method @NonNull public String getString(int);
+    method public boolean isNull(int);
+  }
+
   public final class StatsManager {
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void addConfig(long, byte[]) throws android.app.StatsManager.StatsUnavailableException;
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean addConfiguration(long, byte[]);
@@ -10,6 +22,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] getRegisteredExperimentIds() throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getReports(long) throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException;
+    method @RequiresPermission(android.Manifest.permission.READ_RESTRICTED_STATS) public void query(long, @NonNull String, @NonNull android.app.StatsQuery, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.StatsCursor,android.app.StatsManager.StatsQueryException>) throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException;
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long);
     method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
@@ -18,12 +31,14 @@
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void setPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_RESTRICTED_STATS) public long[] setRestrictedMetricsChangedOperation(long, @NonNull String, @Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
     field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
     field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
     field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
     field public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
     field public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
     field public static final String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE";
+    field public static final String EXTRA_STATS_RESTRICTED_METRIC_IDS = "android.app.extra.STATS_RESTRICTED_METRIC_IDS";
     field public static final String EXTRA_STATS_SUBSCRIPTION_ID = "android.app.extra.STATS_SUBSCRIPTION_ID";
     field public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
     field public static final int PULL_SKIP = 1; // 0x1
@@ -48,11 +63,33 @@
     method public int onPullAtom(int, @NonNull java.util.List<android.util.StatsEvent>);
   }
 
+  public static class StatsManager.StatsQueryException extends android.util.AndroidException {
+    ctor public StatsManager.StatsQueryException(@NonNull String);
+    ctor public StatsManager.StatsQueryException(@NonNull String, @NonNull Throwable);
+  }
+
   public static class StatsManager.StatsUnavailableException extends android.util.AndroidException {
     ctor public StatsManager.StatsUnavailableException(String);
     ctor public StatsManager.StatsUnavailableException(String, Throwable);
   }
 
+  public final class StatsQuery {
+    method @IntRange(from=0) public int getMinSqlClientVersion();
+    method @Nullable public byte[] getPolicyConfig();
+    method @NonNull public String getRawSql();
+    method public int getSqlDialect();
+    field public static final int DIALECT_SQLITE = 1; // 0x1
+    field public static final int DIALECT_UNKNOWN = 0; // 0x0
+  }
+
+  public static final class StatsQuery.Builder {
+    ctor public StatsQuery.Builder(@NonNull String);
+    method @NonNull public android.app.StatsQuery build();
+    method @NonNull public android.app.StatsQuery.Builder setMinSqlClientVersion(@IntRange(from=0) int);
+    method @NonNull public android.app.StatsQuery.Builder setPolicyConfig(@NonNull byte[]);
+    method @NonNull public android.app.StatsQuery.Builder setSqlDialect(int);
+  }
+
 }
 
 package android.os {
@@ -112,12 +149,26 @@
     method @Deprecated public static void writeRaw(@NonNull byte[], int);
     field public static final byte ANNOTATION_ID_DEFAULT_STATE = 6; // 0x6
     field public static final byte ANNOTATION_ID_EXCLUSIVE_STATE = 4; // 0x4
+    field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY = 14; // 0xe
+    field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING = 17; // 0x11
+    field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY = 12; // 0xc
+    field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE = 11; // 0xb
+    field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION = 18; // 0x12
+    field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT = 13; // 0xd
+    field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO = 10; // 0xa
+    field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH = 15; // 0xf
+    field public static final byte ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT = 16; // 0x10
     field public static final byte ANNOTATION_ID_IS_UID = 1; // 0x1
     field public static final byte ANNOTATION_ID_PRIMARY_FIELD = 3; // 0x3
     field public static final byte ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5; // 0x5
+    field public static final byte ANNOTATION_ID_RESTRICTION_CATEGORY = 9; // 0x9
     field public static final byte ANNOTATION_ID_STATE_NESTED = 8; // 0x8
     field public static final byte ANNOTATION_ID_TRIGGER_STATE_RESET = 7; // 0x7
     field public static final byte ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2; // 0x2
+    field public static final int RESTRICTION_CATEGORY_AUTHENTICATION = 3; // 0x3
+    field public static final int RESTRICTION_CATEGORY_DIAGNOSTIC = 1; // 0x1
+    field public static final int RESTRICTION_CATEGORY_FRAUD_AND_ABUSE = 4; // 0x4
+    field public static final int RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE = 2; // 0x2
   }
 
 }
diff --git a/framework/api/system-lint-baseline.txt b/framework/api/system-lint-baseline.txt
new file mode 100644
index 0000000..2ae37de
--- /dev/null
+++ b/framework/api/system-lint-baseline.txt
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.app.StatsManager#ACTION_STATSD_STARTED:
+    Field 'ACTION_STATSD_STARTED' is missing @BroadcastBehavior
+
+
+SdkConstant: android.app.StatsManager#ACTION_STATSD_STARTED:
+    Field 'ACTION_STATSD_STARTED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/framework/java/android/app/StatsCursor.java b/framework/java/android/app/StatsCursor.java
new file mode 100644
index 0000000..29cd241
--- /dev/null
+++ b/framework/java/android/app/StatsCursor.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.SuppressLint;
+import android.database.AbstractCursor;
+import android.database.MatrixCursor;
+
+/**
+ * Custom cursor implementation to hold a cross-process cursor to pass data to caller.
+ *
+ * @hide
+ */
+@SystemApi
+public class StatsCursor extends AbstractCursor {
+    private final MatrixCursor mMatrixCursor;
+    private final int[] mColumnTypes;
+    private final String[] mColumnNames;
+    private final int mRowCount;
+
+    /**
+     * @hide
+     **/
+    public StatsCursor(String[] queryData, String[] columnNames, int[] columnTypes, int rowCount) {
+        mColumnTypes = columnTypes;
+        mColumnNames = columnNames;
+        mRowCount = rowCount;
+        mMatrixCursor = new MatrixCursor(columnNames);
+        for (int i = 0; i < rowCount; i++) {
+            MatrixCursor.RowBuilder builder = mMatrixCursor.newRow();
+            for (int j = 0; j < columnNames.length; j++) {
+                int dataIndex = i * columnNames.length + j;
+                builder.add(columnNames[j], queryData[dataIndex]);
+            }
+        }
+    }
+
+    /**
+     * Returns the numbers of rows in the cursor.
+     *
+     * @return the number of rows in the cursor.
+     */
+    @Override
+    public int getCount() {
+        return mRowCount;
+    }
+
+    /**
+     * Returns a string array holding the names of all of the columns in the
+     * result set in the order in which they were listed in the result.
+     *
+     * @return the names of the columns returned in this query.
+     */
+    @Override
+    @NonNull
+    public String[] getColumnNames() {
+        return mColumnNames;
+    }
+
+    /**
+     * Returns the value of the requested column as a String.
+     *
+     * @param column the zero-based index of the target column.
+     * @return the value of that column as a String.
+     */
+    @Override
+    @NonNull
+    public String getString(int column) {
+        return mMatrixCursor.getString(column);
+    }
+
+    /**
+     * Returns the value of the requested column as a short.
+     *
+     * @param column the zero-based index of the target column.
+     * @return the value of that column as a short.
+     */
+    @Override
+    @SuppressLint("NoByteOrShort")
+    public short getShort(int column) {
+        return mMatrixCursor.getShort(column);
+    }
+
+    /**
+     * Returns the value of the requested column as an int.
+     *
+     * @param column the zero-based index of the target column.
+     * @return the value of that column as an int.
+     */
+    @Override
+    public int getInt(int column) {
+        return mMatrixCursor.getInt(column);
+    }
+
+    /**
+     * Returns the value of the requested column as a long.
+     *
+     * @param column the zero-based index of the target column.
+     * @return the value of that column as a long.
+     */
+    @Override
+    public long getLong(int column) {
+        return mMatrixCursor.getLong(column);
+    }
+
+    /**
+     * Returns the value of the requested column as a float.
+     *
+     * @param column the zero-based index of the target column.
+     * @return the value of that column as a float.
+     */
+    @Override
+    public float getFloat(int column) {
+        return mMatrixCursor.getFloat(column);
+    }
+
+    /**
+     * Returns the value of the requested column as a double.
+     *
+     * @param column the zero-based index of the target column.
+     * @return the value of that column as a double.
+     */
+    @Override
+    public double getDouble(int column) {
+        return mMatrixCursor.getDouble(column);
+    }
+
+    /**
+     * Returns <code>true</code> if the value in the indicated column is null.
+     *
+     * @param column the zero-based index of the target column.
+     * @return whether the column value is null.
+     */
+    @Override
+    public boolean isNull(int column) {
+        return mMatrixCursor.isNull(column);
+    }
+
+    /**
+     * Returns the data type of the given column's value.
+     *
+     * @param column the zero-based index of the target column.
+     * @return column value type
+     */
+    @Override
+    public int getType(int column) {
+        return mColumnTypes[column];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean onMove(int oldPosition, int newPosition) {
+        return mMatrixCursor.moveToPosition(newPosition);
+    }
+}
diff --git a/framework/java/android/app/StatsManager.java b/framework/java/android/app/StatsManager.java
index 43e085e..0fc9f00 100644
--- a/framework/java/android/app/StatsManager.java
+++ b/framework/java/android/app/StatsManager.java
@@ -17,6 +17,7 @@
 
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+import static android.Manifest.permission.READ_RESTRICTED_STATS;
 import static android.provider.DeviceConfig.NAMESPACE_STATSD_JAVA;
 
 import android.annotation.CallbackExecutor;
@@ -26,9 +27,12 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IPullAtomCallback;
 import android.os.IPullAtomResultReceiver;
 import android.os.IStatsManagerService;
+import android.os.IStatsQueryCallback;
+import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.StatsFrameworkInitializer;
@@ -38,6 +42,8 @@
 import android.util.StatsEvent;
 import android.util.StatsEventParcel;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
@@ -104,6 +110,12 @@
             "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
 
     /**
+     * Long array extra of the restricted metric ids present for the client.
+     */
+    public static final String EXTRA_STATS_RESTRICTED_METRIC_IDS =
+            "android.app.extra.STATS_RESTRICTED_METRIC_IDS";
+
+    /**
      * Broadcast Action: Statsd has started.
      * Configurations and PendingIntents can now be sent to it.
      */
@@ -372,6 +384,115 @@
         }
     }
 
+    /**
+     * Registers the operation that is called whenever there is a change in the restricted metrics
+     * for a specified config that are present for this client. This operation allows statsd to
+     * inform the client about the current restricted metric ids available to be queried for the
+     * specified config. This call can block on statsd.
+     *
+     * If there is no config in statsd that matches the provided config package and key, an empty
+     * list is returned. The pending intent will be tracked, and the operation will be called
+     * whenever a matching config is added.
+     *
+     * @param configKey The configKey passed by the package that added the config in
+     *                  StatsManager#addConfig
+     * @param configPackage The package that added the config in StatsManager#addConfig
+     * @param pendingIntent the PendingIntent to use when broadcasting info to caller.
+     *                      May be null, in which case it removes any associated pending intent
+     *                      for this client.
+     * @return A list of metric ids identifying the restricted metrics that are currently available
+     *         to be queried for the specified config.
+     *         If the pendingIntent is null, this will be an empty list.
+     * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
+     */
+    @RequiresPermission(READ_RESTRICTED_STATS)
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public @NonNull long[] setRestrictedMetricsChangedOperation(long configKey,
+            @NonNull String configPackage,
+            @Nullable PendingIntent pendingIntent)
+            throws StatsUnavailableException {
+        synchronized (sLock) {
+            try {
+                IStatsManagerService service = getIStatsManagerServiceLocked();
+                if (pendingIntent == null) {
+                    service.removeRestrictedMetricsChangedOperation(configKey, configPackage);
+                    return new long[0];
+                } else {
+                    return service.setRestrictedMetricsChangedOperation(pendingIntent,
+                            configKey, configPackage);
+                }
+
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to connect to statsmanager "
+                        + "when registering restricted metrics  listener.");
+                throw new StatsUnavailableException("could not connect", e);
+            } catch (SecurityException e) {
+                throw new StatsUnavailableException(e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * Queries the underlying service based on query received and populates the OutcomeReceiver via
+     * callback. This call is blocking on statsd being available, but is otherwise nonblocking.
+     * i.e. the call can return before the query processing is done.
+     * <p>
+     * Two types of tables are supported: Metric tables and the device information table.
+     * </p>
+     * <p>
+     * The device information table is named device_info and contains the following columns:
+     * sdkVersion, model, product, hardware, device, osBuild, fingerprint, brand, manufacturer, and
+     * board. These columns correspond to {@link Build.VERSION.SDK_INT}, {@link Build.MODEL},
+     * {@link Build.PRODUCT}, {@link Build.HARDWARE}, {@link Build.DEVICE}, {@link Build.ID},
+     * {@link Build.FINGERPRINT}, {@link Build.BRAND}, {@link Build.MANUFACTURER},
+     * {@link Build.BOARD} respectively.
+     * </p>
+     * <p>
+     * The metric tables are named metric_METRIC_ID where METRIC_ID is the metric id that is part
+     * of the wire encoded config passed to {@link #addConfig(long, byte[])}. If the metric id is
+     * negative, then the '-' character is replaced with 'n' in the table name. Each metric table
+     * contains the 3 columns followed by n columns of the following form: atomId,
+     * elapsedTimestampNs, wallTimestampNs, field_1, field_2, field_3 ... field_n. These
+     * columns correspond to to the id of the atom from frameworks/proto_logging/stats/atoms.proto,
+     * time when the atom is recorded, and the data fields within each atom.
+     * </p>
+     * @param configKey The configKey passed by the package that added
+     *                        the config being queried in StatsManager#addConfig
+     * @param configPackage The package that added the config being queried in
+     *                        StatsManager#addConfig
+     * @param query the query object encapsulating a sql-string and necessary config to query
+     *              underlying sql-based data store.
+     * @param executor the executor on which outcomeReceiver will be invoked.
+     * @param outcomeReceiver the receiver to be populated with cursor pointing to result data.
+     */
+    @RequiresPermission(READ_RESTRICTED_STATS)
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public void query(long configKey, @NonNull String configPackage, @NonNull StatsQuery query,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<StatsCursor, StatsQueryException> outcomeReceiver)
+            throws StatsUnavailableException {
+        if(query.getSqlDialect() != StatsQuery.DIALECT_SQLITE) {
+            executor.execute(() -> {
+                outcomeReceiver.onError(new StatsQueryException("Unsupported Sql Dialect"));
+            });
+            return;
+        }
+
+        StatsQueryCallbackInternal callbackInternal =
+                new StatsQueryCallbackInternal(outcomeReceiver, executor);
+        synchronized (sLock) {
+            try {
+                IStatsManagerService service = getIStatsManagerServiceLocked();
+                service.querySql(query.getRawSql(), query.getMinSqlClientVersion(),
+                        query.getPolicyConfig(), callbackInternal, configKey,
+                        configPackage);
+            } catch (RemoteException | IllegalStateException e) {
+                throw new StatsUnavailableException("could not connect", e);
+            }
+        }
+    }
+
+
     // TODO: Temporary for backwards compatibility. Remove.
     /**
      * @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)}
@@ -734,6 +855,52 @@
         return mStatsManagerService;
     }
 
+    private static class StatsQueryCallbackInternal extends IStatsQueryCallback.Stub {
+        OutcomeReceiver<StatsCursor, StatsQueryException> queryCallback;
+        Executor mExecutor;
+
+        StatsQueryCallbackInternal(OutcomeReceiver<StatsCursor, StatsQueryException> queryCallback,
+                @NonNull @CallbackExecutor Executor executor) {
+            this.queryCallback = queryCallback;
+            this.mExecutor = executor;
+        }
+
+        @Override
+        public void sendResults(String[] queryData, String[] columnNames, int[] columnTypes,
+                int rowCount) {
+            if (!SdkLevel.isAtLeastU()) {
+                throw new IllegalStateException(
+                        "StatsManager#query is not available before Android U");
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    StatsCursor cursor = new StatsCursor(queryData, columnNames, columnTypes,
+                            rowCount);
+                    queryCallback.onResult(cursor);
+                });
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void sendFailure(String error) {
+            if (!SdkLevel.isAtLeastU()) {
+                throw new IllegalStateException(
+                        "StatsManager#query is not available before Android U");
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    queryCallback.onError(new StatsQueryException(error));
+                });
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
     /**
      * Exception thrown when communication with the stats service fails (eg if it is not available).
      * This might be thrown early during boot before the stats service has started or if it crashed.
@@ -748,12 +915,22 @@
         }
     }
 
-    private static final String USE_FILE_DESCRIPTOR_FLAG = "use_file_descriptor";
+    /**
+     * Exception thrown when executing a query in statsd fails for any reason. This might be thrown
+     * if the query is malformed or if there is a database error when executing the query.
+     */
+    public static class StatsQueryException extends AndroidException {
+        public StatsQueryException(@NonNull String reason) {
+            super("Failed to query statsd: " + reason);
+        }
+
+        public StatsQueryException(@NonNull String reason, @NonNull Throwable e) {
+            super("Failed to query statsd: " + reason, e);
+        }
+    }
 
     private static boolean getUseFileDescriptor() {
-        return SdkLevel.isAtLeastT()
-                && DeviceConfig.getBoolean(
-                NAMESPACE_STATSD_JAVA, USE_FILE_DESCRIPTOR_FLAG, /* defaultValue= */ false);
+        return SdkLevel.isAtLeastT();
     }
 
     private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 20; // 20MB
diff --git a/framework/java/android/app/StatsQuery.java b/framework/java/android/app/StatsQuery.java
new file mode 100644
index 0000000..59f8597
--- /dev/null
+++ b/framework/java/android/app/StatsQuery.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents a query that contains information required for StatsManager to return relevant metric
+ * data.
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsQuery {
+    /**
+     * Default value for SQL dialect.
+     */
+    public static final int DIALECT_UNKNOWN = 0;
+
+    /**
+     * Query passed is of SQLite dialect.
+     */
+    public static final int DIALECT_SQLITE = 1;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"DIALECT_"}, value = {DIALECT_UNKNOWN, DIALECT_SQLITE})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface SqlDialect {
+    }
+
+    private final int sqlDialect;
+    private final String rawSql;
+    private final int minClientSqlVersion;
+    private final byte[] policyConfig;
+    private StatsQuery(int sqlDialect, @NonNull String rawSql, int minClientSqlVersion,
+            @Nullable byte[] policyConfig) {
+        this.sqlDialect = sqlDialect;
+        this.rawSql = rawSql;
+        this.minClientSqlVersion = minClientSqlVersion;
+        this.policyConfig = policyConfig;
+    }
+
+    /**
+     * Returns the SQL dialect of the query.
+     */
+    public @SqlDialect int getSqlDialect() {
+        return sqlDialect;
+    }
+
+    /**
+     * Returns the raw SQL of the query.
+     */
+    @NonNull
+    public String getRawSql() {
+        return rawSql;
+    }
+
+    /**
+     * Returns the minimum SQL client library version required to execute the query.
+     */
+    @IntRange(from = 0)
+    public int getMinSqlClientVersion() {
+        return minClientSqlVersion;
+    }
+
+    /**
+     * Returns the wire-encoded StatsPolicyConfig proto that contains information to verify the
+     * query against a policy defined on the underlying data. Returns null if no policy was set.
+     */
+    @Nullable
+    public byte[] getPolicyConfig() {
+        return policyConfig;
+    }
+
+    /**
+     * Builder for constructing a StatsQuery object.
+     * <p>Usage:</p>
+     * <code>
+     * StatsQuery statsQuery = new StatsQuery.Builder("SELECT * from table")
+     * .setSqlDialect(StatsQuery.DIALECT_SQLITE)
+     * .setMinClientSqlVersion(1)
+     * .build();
+     * </code>
+     */
+    public static final class Builder {
+        private int sqlDialect;
+        private String rawSql;
+        private int minSqlClientVersion;
+        private byte[] policyConfig;
+
+        /**
+         * Returns a new StatsQuery.Builder object for constructing StatsQuery for
+         * StatsManager#query
+         */
+        public Builder(@NonNull final String rawSql) {
+            if (rawSql == null) {
+                throw new IllegalArgumentException("rawSql must not be null");
+            }
+            this.rawSql = rawSql;
+            this.sqlDialect = DIALECT_SQLITE;
+            this.minSqlClientVersion = 1;
+            this.policyConfig = null;
+        }
+
+        /**
+         * Sets the SQL dialect of the query.
+         *
+         * @param sqlDialect The SQL dialect of the query.
+         */
+        @NonNull
+        public Builder setSqlDialect(@SqlDialect final int sqlDialect) {
+            this.sqlDialect = sqlDialect;
+            return this;
+        }
+
+        /**
+         * Sets the minimum SQL client library version required to execute the query.
+         *
+         * @param minSqlClientVersion The minimum SQL client version required to execute the query.
+         */
+        @NonNull
+        public Builder setMinSqlClientVersion(@IntRange(from = 0) final int minSqlClientVersion) {
+            if (minSqlClientVersion < 0) {
+                throw new IllegalArgumentException("minSqlClientVersion must be a "
+                        + "positive integer");
+            }
+            this.minSqlClientVersion = minSqlClientVersion;
+            return this;
+        }
+
+        /**
+         * Sets the wire-encoded StatsPolicyConfig proto that contains information to verify the
+         * query against a policy defined on the underlying data.
+         *
+         * @param policyConfig The wire-encoded StatsPolicyConfig proto.
+         */
+        @NonNull
+        public Builder setPolicyConfig(@NonNull final byte[] policyConfig) {
+            this.policyConfig = policyConfig;
+            return this;
+        }
+
+        /**
+         * Builds a new instance of {@link StatsQuery}.
+         *
+         * @return A new instance of {@link StatsQuery}.
+         */
+        @NonNull
+        public StatsQuery build() {
+            return new StatsQuery(sqlDialect, rawSql, minSqlClientVersion, policyConfig);
+        }
+    }
+}
diff --git a/framework/java/android/util/StatsLog.java b/framework/java/android/util/StatsLog.java
index 28884c1..f38751a 100644
--- a/framework/java/android/util/StatsLog.java
+++ b/framework/java/android/util/StatsLog.java
@@ -20,17 +20,24 @@
 import static android.Manifest.permission.PACKAGE_USAGE_STATS;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.os.Build;
 import android.os.IStatsd;
 import android.os.Process;
 import android.util.proto.ProtoOutputStream;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.statsd.StatsdStatsLog;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * StatsLog provides an API for developers to send events to statsd. The events can be used to
  * define custom metrics inside statsd.
@@ -46,80 +53,290 @@
     private static final int EXPERIMENT_IDS_FIELD_ID = 1;
 
     /**
-    * Annotation ID constant for logging UID field.
-    *
-    * @hide
-    */
+     * Annotation ID constant for logging UID field.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
     @SuppressLint("NoByteOrShort")
     @SystemApi
     public static final byte ANNOTATION_ID_IS_UID = 1;
 
     /**
-    * Annotation ID constant to indicate logged atom event's timestamp should be truncated.
-    *
-    * @hide
-    */
+     * Annotation ID constant to indicate logged atom event's timestamp should be truncated.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
     @SuppressLint("NoByteOrShort")
     @SystemApi
     public static final byte ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
 
     /**
-    * Annotation ID constant for a state atom's primary field.
-    *
-    * @hide
-    */
+     * Annotation ID constant for a state atom's primary field.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
     @SuppressLint("NoByteOrShort")
     @SystemApi
     public static final byte ANNOTATION_ID_PRIMARY_FIELD = 3;
 
     /**
-    * Annotation ID constant for state atom's state field.
-    *
-    * @hide
-    */
+     * Annotation ID constant for state atom's state field.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
     @SuppressLint("NoByteOrShort")
     @SystemApi
     public static final byte ANNOTATION_ID_EXCLUSIVE_STATE = 4;
 
     /**
-    * Annotation ID constant to indicate the first UID in the attribution chain
-    * is a primary field.
-    * Should only be used for attribution chain fields.
-    *
-    * @hide
-    */
+     * Annotation ID constant to indicate the first UID in the attribution chain
+     * is a primary field.
+     * Should only be used for attribution chain fields.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
     @SuppressLint("NoByteOrShort")
     @SystemApi
     public static final byte ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5;
 
     /**
-    * Annotation ID constant to indicate which state is default for the state atom.
-    *
-    * @hide
-    */
+     * Annotation ID constant to indicate which state is default for the state atom.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
     @SuppressLint("NoByteOrShort")
     @SystemApi
     public static final byte ANNOTATION_ID_DEFAULT_STATE = 6;
 
     /**
-    * Annotation ID constant to signal all states should be reset to the default state.
-    *
-    * @hide
-    */
+     * Annotation ID constant to signal all states should be reset to the default state.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
     @SuppressLint("NoByteOrShort")
     @SystemApi
     public static final byte ANNOTATION_ID_TRIGGER_STATE_RESET = 7;
 
     /**
-    * Annotation ID constant to indicate state changes need to account for nesting.
-    * This should only be used with binary state atoms.
-    *
-    * @hide
-    */
+     * Annotation ID constant to indicate state changes need to account for nesting.
+     * This should only be used with binary state atoms.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
     @SuppressLint("NoByteOrShort")
     @SystemApi
     public static final byte ANNOTATION_ID_STATE_NESTED = 8;
 
+    /**
+     * Annotation ID constant to indicate the restriction category of an atom.
+     * This annotation must only be attached to the atom id. This is an int annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_RESTRICTION_CATEGORY = 9;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains peripheral device info.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO = 10;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains app usage information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE = 11;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains app activity information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY = 12;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains health connect information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT = 13;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains accessibility information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY = 14;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains system search information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH = 15;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains user engagement information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT = 16;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains ambient sensing information.
+     * This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING = 17;
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains demographic classification
+     * information. This is a bool annotation.
+     *
+     * The ID is a byte since StatsEvent.addBooleanAnnotation() and StatsEvent.addIntAnnotation()
+     * accept byte as the type for annotation ids to save space.
+     *
+     * @hide
+     */
+    @SuppressLint("NoByteOrShort")
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final byte ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION = 18;
+
+
+    /** @hide */
+    @IntDef(prefix = { "RESTRICTION_CATEGORY_" }, value = {
+            RESTRICTION_CATEGORY_DIAGNOSTIC,
+            RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE,
+            RESTRICTION_CATEGORY_AUTHENTICATION,
+            RESTRICTION_CATEGORY_FRAUD_AND_ABUSE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RestrictionCategory {}
+
+    /**
+     * Restriction category for atoms about diagnostics.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final int RESTRICTION_CATEGORY_DIAGNOSTIC = 1;
+
+    /**
+     * Restriction category for atoms about system intelligence.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final int RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE = 2;
+
+    /**
+     * Restriction category for atoms about authentication.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final int RESTRICTION_CATEGORY_AUTHENTICATION = 3;
+
+    /**
+     * Restriction category for atoms about fraud and abuse.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static final int RESTRICTION_CATEGORY_FRAUD_AND_ABUSE = 4;
+
     private StatsLog() {
     }
 
diff --git a/framework/test/hostsidetests/Android.bp b/framework/test/hostsidetests/Android.bp
index e0b7eb0..b3e253c 100644
--- a/framework/test/hostsidetests/Android.bp
+++ b/framework/test/hostsidetests/Android.bp
@@ -24,7 +24,7 @@
         "androidx.test.runner",
         "androidx.test.core",
         "statsdprotolite",
-        "truth-prebuilt",
+        "truth",
         "framework-statsd.stubs.module_lib",
     ],
     compile_multilib: "both",
@@ -42,7 +42,7 @@
         "androidx.test.runner",
         "androidx.test.core",
         "statsdprotolite",
-        "truth-prebuilt",
+        "truth",
         "framework-statsd.stubs.module_lib",
     ],
     compile_multilib: "both",
@@ -54,10 +54,10 @@
 
 java_test_host {
     name: "FrameworkStatsdHostTest",
-    srcs: [ "src/**/*.java" ],
+    srcs: ["src/**/*.java"],
     libs: [
         "tradefed",
-        "truth-prebuilt",
+        "truth",
     ],
     test_suites: [
         "device-tests",
diff --git a/framework/test/hostsidetests/app/src/com/android/tests/statsd/framework/appnopermission/StatsdPermissionTest.java b/framework/test/hostsidetests/app/src/com/android/tests/statsd/framework/appnopermission/StatsdPermissionTest.java
index d8c43b2..d42ab9e 100644
--- a/framework/test/hostsidetests/app/src/com/android/tests/statsd/framework/appnopermission/StatsdPermissionTest.java
+++ b/framework/test/hostsidetests/app/src/com/android/tests/statsd/framework/appnopermission/StatsdPermissionTest.java
@@ -61,7 +61,11 @@
 
     e = assertThrows(
         StatsManager.StatsUnavailableException.class, () -> statsManager.getReports(1234));
-    assertThat(e).hasCauseThat().isInstanceOf(SecurityException.class);
+    // expectations are:
+    // - for Android T+ receive IllegalStateException
+    // - for previous versions receive SecurityExceptions
+    assertThat(e.getCause().getClass()).
+            isAnyOf(SecurityException.class, IllegalStateException.class);
 
     e = assertThrows(
         StatsManager.StatsUnavailableException.class, () -> statsManager.getStatsMetadata());
@@ -101,4 +105,4 @@
     // Setting a pull atom callback is not tested because it is oneway,
     // so the security exception would not propagate to the client.
   }
-}
\ No newline at end of file
+}
diff --git a/framework/test/unittests/Android.bp b/framework/test/unittests/Android.bp
index 243328c..14424f8 100644
--- a/framework/test/unittests/Android.bp
+++ b/framework/test/unittests/Android.bp
@@ -19,11 +19,11 @@
 android_test {
     name: "FrameworkStatsdTest",
     sdk_version: "module_current",
-    srcs: [ "**/*.java" ],
+    srcs: ["**/*.java"],
     manifest: "AndroidManifest.xml",
     static_libs: [
         "androidx.test.rules",
-        "truth-prebuilt",
+        "truth",
         "modules-utils-build",
     ],
     libs: [
diff --git a/framework/test/unittests/AndroidTest.xml b/framework/test/unittests/AndroidTest.xml
index e32f16e..4ab7b64 100644
--- a/framework/test/unittests/AndroidTest.xml
+++ b/framework/test/unittests/AndroidTest.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Runs Tests for Statsd.">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="FrameworkStatsdTest.apk" />
         <option name="install-arg" value="-g" />
     </target_preparer>
diff --git a/lib/libstatsgtestmatchers/Android.bp b/lib/libstatsgtestmatchers/Android.bp
index 38839e0..ae26066 100644
--- a/lib/libstatsgtestmatchers/Android.bp
+++ b/lib/libstatsgtestmatchers/Android.bp
@@ -24,8 +24,8 @@
 cc_test_library {
     name: "libstatsgtestmatchers",
     srcs: [
-        ":statsd_internal_protos",
-        ":libstats_internal_protos",
+        ":libstats_log_protos",
+        ":libstats_subscription_protos",
     ],
     export_include_dirs: ["include"],
     proto: {
@@ -46,7 +46,7 @@
         "-Wall",
         "-Werror",
         "-Wthread-safety",
-        "-Wdeprecated-declarations",
+        "-Wno-deprecated-declarations",
     ],
     visibility: [
         "//packages/modules/StatsD/lib/libstatspull",
diff --git a/lib/libstatsgtestmatchers/include/gtest_matchers.h b/lib/libstatsgtestmatchers/include/gtest_matchers.h
index c6729de..3d9b792 100644
--- a/lib/libstatsgtestmatchers/include/gtest_matchers.h
+++ b/lib/libstatsgtestmatchers/include/gtest_matchers.h
@@ -206,20 +206,39 @@
 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,
-        REPEATED_PROPERTY_MATCHER(ShellData, atom, EqAtom)
+        REPEATED_PROPERTY_MATCHER(ShellData, atom, EqAtom),
+        REPEATED_PROPERTY_EQ(ShellData, elapsed_timestamp_nanos)
 );
 TYPE_PRINTER(ShellData,
         REPEATED_PROPERTY_PRINT(atom)
+        REPEATED_PROPERTY_PRINT(elapsed_timestamp_nanos)
 );
 
 // clang-format on
diff --git a/lib/libstatspull/Android.bp b/lib/libstatspull/Android.bp
index de0ae47..28c0a70 100644
--- a/lib/libstatspull/Android.bp
+++ b/lib/libstatspull/Android.bp
@@ -37,38 +37,21 @@
     shared_libs: [
         "libbinder_ndk",
         "liblog",
+        "libstatssocket",
     ],
     static_libs: [
         "libutils",
         "statsd-aidl-ndk",
     ],
-    target: {
-        android: {
-            shared_libs: ["libstatssocket"],
-        },
-        host: {
-            static_libs: ["libstatssocket"],
-        },
-    },
 }
 
-cc_library {
+cc_library_shared {
     name: "libstatspull",
     defaults: [
         "libstatspull_defaults",
     ],
     host_supported: true,
     target: {
-        android: {
-            static: {
-                enabled: false,
-            },
-        },
-        host: {
-            shared: {
-                enabled: false,
-            },
-        },
         darwin: {
             enabled: false,
         },
@@ -94,20 +77,6 @@
     export_include_dirs: ["include"],
 }
 
-// ONLY USE IN TESTS.
-cc_library_static {
-    name: "libstatspull_private",
-    defaults: [
-        "libstatspull_defaults",
-    ],
-    cflags: [
-        "-DLIB_STATS_PULL_TESTS_FLAG",
-    ],
-    visibility: [
-        "//packages/modules/StatsD/apex/tests/libstatspull",
-    ],
-}
-
 // Note: These unit tests only test PullAtomMetadata and subscriptions
 // For full E2E tests of pullers, use LibStatsPullTests
 cc_test {
@@ -169,4 +138,5 @@
     ],
     require_root: true,
     min_sdk_version: "30",
+    test_for: ["com.android.os.statsd"],
 }
diff --git a/lib/libstatspull/libstatspull.map.txt b/lib/libstatspull/libstatspull.map.txt
index 30641e4..a76342c 100644
--- a/lib/libstatspull/libstatspull.map.txt
+++ b/lib/libstatspull/libstatspull.map.txt
@@ -1,21 +1,21 @@
 LIBSTATSPULL {
     global:
-        AStatsManager_PullAtomMetadata_obtain; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_release; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_setCoolDownMillis; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_getCoolDownMillis; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_setTimeoutMillis; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_getTimeoutMillis; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_setAdditiveFields; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_getNumAdditiveFields; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_getAdditiveFields; # apex # introduced=30
-        AStatsEventList_addStatsEvent; # apex # introduced=30
-        AStatsManager_setPullAtomCallback; # apex # introduced=30
-        AStatsManager_clearPullAtomCallback; # apex # introduced=30
+        AStatsManager_PullAtomMetadata_obtain; # apex introduced=30
+        AStatsManager_PullAtomMetadata_release; # apex introduced=30
+        AStatsManager_PullAtomMetadata_setCoolDownMillis; # apex introduced=30
+        AStatsManager_PullAtomMetadata_getCoolDownMillis; # apex introduced=30
+        AStatsManager_PullAtomMetadata_setTimeoutMillis; # apex introduced=30
+        AStatsManager_PullAtomMetadata_getTimeoutMillis; # apex introduced=30
+        AStatsManager_PullAtomMetadata_setAdditiveFields; # apex introduced=30
+        AStatsManager_PullAtomMetadata_getNumAdditiveFields; # apex introduced=30
+        AStatsManager_PullAtomMetadata_getAdditiveFields; # apex introduced=30
+        AStatsEventList_addStatsEvent; # apex introduced=30
+        AStatsManager_setPullAtomCallback; # apex introduced=30
+        AStatsManager_clearPullAtomCallback; # apex introduced=30
 
-        AStatsManager_addSubscription; # apex # introduced=UpsideDownCake
-        AStatsManager_removeSubscription; # apex # introduced=UpsideDownCake
-        AStatsManager_flushSubscription; # apex # introduced=UpsideDownCake
+        AStatsManager_addSubscription; # apex introduced=UpsideDownCake
+        AStatsManager_removeSubscription; # apex introduced=UpsideDownCake
+        AStatsManager_flushSubscription; # apex introduced=UpsideDownCake
     local:
         *;
 };
diff --git a/lib/libstatspull/stats_pull_atom_callback.cpp b/lib/libstatspull/stats_pull_atom_callback.cpp
index 3fdf243..b880f0a 100644
--- a/lib/libstatspull/stats_pull_atom_callback.cpp
+++ b/lib/libstatspull/stats_pull_atom_callback.cpp
@@ -121,8 +121,6 @@
         // Convert stats_events into StatsEventParcels.
         std::vector<StatsEventParcel> parcels;
 
-        // Resolves fuzz build failure in b/161575591.
-#if defined(__ANDROID_APEX__) || defined(LIB_STATS_PULL_TESTS_FLAG)
         for (int i = 0; i < statsEventList.data.size(); i++) {
             size_t size;
             uint8_t* buffer = AStatsEvent_getBuffer(statsEventList.data[i], &size);
@@ -133,7 +131,6 @@
             p.buffer.assign(buffer, buffer + size);
             parcels.push_back(std::move(p));
         }
-#endif
 
         Status status = resultReceiver->pullFinished(atomTag, success, parcels);
         if (!status.isOk()) {
@@ -276,11 +273,8 @@
 
 public:
     ~CallbackOperationsHandler() {
-        for (auto& workThread : mWorkThreads) {
-            if (workThread.joinable()) {
-                mCondition.notify_one();
-                workThread.join();
-            }
+        if (mWorkThread.joinable()) {
+            mWorkThread.join();
         }
     }
 
@@ -296,9 +290,7 @@
         registerCmd->callback = std::move(callback);
         pushToQueue(std::move(registerCmd));
 
-        std::thread registerThread(&CallbackOperationsHandler::processCommands, this,
-                                   statsProvider);
-        mWorkThreads.push_back(std::move(registerThread));
+        startWorkerThread();
     }
 
     void unregisterCallback(int atomTag) {
@@ -307,15 +299,13 @@
         unregisterCmd->atomTag = atomTag;
         pushToQueue(std::move(unregisterCmd));
 
-        std::thread unregisterThread(&CallbackOperationsHandler::processCommands, this,
-                                     statsProvider);
-        mWorkThreads.push_back(std::move(unregisterThread));
+        startWorkerThread();
     }
 
 private:
-    std::vector<std::thread> mWorkThreads;
+    std::atomic_bool mThreadAlive = false;
+    std::thread mWorkThread;
 
-    std::condition_variable mCondition;
     std::mutex mMutex;
     std::queue<std::unique_ptr<Cmd>> mCmdQueue;
 
@@ -323,11 +313,20 @@
     }
 
     void pushToQueue(std::unique_ptr<Cmd> cmd) {
-        {
-            std::unique_lock<std::mutex> lock(mMutex);
-            mCmdQueue.push(std::move(cmd));
+        std::unique_lock<std::mutex> lock(mMutex);
+        mCmdQueue.push(std::move(cmd));
+    }
+
+    void startWorkerThread() {
+        // Only spawn one thread to manage requests
+        if (mThreadAlive) {
+            return;
         }
-        mCondition.notify_one();
+        mThreadAlive = true;
+        if (mWorkThread.joinable()) {
+            mWorkThread.join();
+        }
+        mWorkThread = std::thread(&CallbackOperationsHandler::processCommands, this, statsProvider);
     }
 
     void processCommands(std::shared_ptr<StatsdProvider> statsProvider) {
@@ -337,37 +336,41 @@
          */
         const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
 
-        /**
-         * To guarantee sequential commands processing we need to lock mutex queue
-         */
-        std::unique_lock<std::mutex> lock(mMutex);
-        /**
-         * This should never really block in practice, since the command was already queued
-         * from the main thread by registerCallback or unregisterCallback.
-         * We are putting command to the queue, and only after a worker thread is created,
-         * which will pop a single command from a queue and will be terminated after processing.
-         * It makes producer/consumer as 1:1 match
-         */
-        if (mCmdQueue.empty()) {
-            mCondition.wait(lock, [this] { return !this->mCmdQueue.empty(); });
-        }
-
-        std::unique_ptr<Cmd> cmd = std::move(mCmdQueue.front());
-        mCmdQueue.pop();
-
         if (!statsService) {
-            // Statsd not available - dropping command request
+            // Statsd not available - dropping all submitted command requests
+            std::queue<std::unique_ptr<Cmd>> emptyQueue;
+            std::unique_lock<std::mutex> lock(mMutex);
+            mCmdQueue.swap(emptyQueue);
+            mThreadAlive = false;
             return;
         }
 
-        switch (cmd->type) {
-            case Cmd::CMD_REGISTER: {
-                registerStatsPullAtomCallbackBlocking(cmd->atomTag, statsProvider, cmd->callback);
-                break;
+        while (true) {
+            std::unique_ptr<Cmd> cmd = nullptr;
+            {
+                /**
+                 * To guarantee sequential commands processing we need to lock mutex queue
+                 */
+                std::unique_lock<std::mutex> lock(mMutex);
+                if (mCmdQueue.empty()) {
+                    mThreadAlive = false;
+                    return;
+                }
+
+                cmd = std::move(mCmdQueue.front());
+                mCmdQueue.pop();
             }
-            case Cmd::CMD_UNREGISTER: {
-                unregisterStatsPullAtomCallbackBlocking(cmd->atomTag, statsProvider);
-                break;
+
+            switch (cmd->type) {
+                case Cmd::CMD_REGISTER: {
+                    registerStatsPullAtomCallbackBlocking(cmd->atomTag, statsProvider,
+                                                          cmd->callback);
+                    break;
+                }
+                case Cmd::CMD_UNREGISTER: {
+                    unregisterStatsPullAtomCallbackBlocking(cmd->atomTag, statsProvider);
+                    break;
+                }
             }
         }
     }
diff --git a/lib/libstatssocket/Android.bp b/lib/libstatssocket/Android.bp
index a6d103e..fce6a75 100644
--- a/lib/libstatssocket/Android.bp
+++ b/lib/libstatssocket/Android.bp
@@ -25,41 +25,39 @@
     name: "libstatssocket_defaults",
     srcs: [
         "stats_buffer_writer.c",
+        "stats_buffer_writer_queue.cpp",
         "stats_event.c",
         "stats_socket.c",
         "statsd_writer.cpp",
+        "stats_socket_loss_reporter.cpp",
+        "utils.cpp",
     ],
+    generated_sources: ["stats_statsdsocketlog.cpp"],
+    generated_headers: ["stats_statsdsocketlog.h"],
     export_include_dirs: ["include"],
-    header_libs: ["liblog_headers"],
+    header_libs: [
+        "libcutils_headers",
+        "liblog_headers",
+    ],
     cflags: [
         "-Wall",
         "-Werror",
         "-Wthread-safety",
+
+        // for local testing & benchmarking with statsd_benchmark
+        // "-DENABLE_BENCHMARK_SUPPORT",
     ],
     static_libs: [
         "libbase",
     ],
 }
 
-cc_library {
+cc_library_shared {
     name: "libstatssocket",
     defaults: [
         "libstatssocket_defaults",
     ],
     host_supported: true,
-    target: {
-        // On android, libstatssocket should only be linked as a shared lib
-        android: {
-            static: {
-                enabled: false,
-            },
-        },
-        host: {
-            shared: {
-                enabled: false,
-            },
-        },
-    },
     stl: "libc++_static",
 
     // enumerate stable entry points for APEX use
@@ -76,18 +74,6 @@
     min_sdk_version: "30",
 }
 
-//TODO (b/149842105): Figure out if there is a better solution for this.
-cc_test_library {
-    name: "libstatssocket_private",
-    defaults: [
-        "libstatssocket_defaults",
-    ],
-    visibility: [
-        "//packages/modules/StatsD/apex/tests/libstatspull",
-        "//packages/modules/StatsD/statsd",
-    ],
-}
-
 cc_library_headers {
     name: "libstatssocket_headers",
     export_include_dirs: ["include"],
@@ -103,7 +89,11 @@
     srcs: [
         "tests/stats_event_test.cpp",
         "tests/stats_writer_test.cpp",
+        "tests/stats_buffer_writer_queue_test.cpp",
+        "tests/stats_socketlog_test.cpp",
     ],
+    generated_sources: ["stats_statsdsocketlog.cpp"],
+    generated_headers: ["stats_statsdsocketlog.h"],
     cflags: [
         "-Wall",
         "-Werror",
@@ -112,10 +102,10 @@
     static_libs: [
         "libbase",
         "libgmock",
-        "libstatssocket_private",
     ],
     shared_libs: [
         "libutils",
+        "libstatssocket",
     ],
     test_suites: [
         "device-tests",
@@ -135,4 +125,56 @@
     },
     require_root: true,
     min_sdk_version: "30",
+    test_for: ["com.android.os.statsd"],
+}
+
+genrule {
+    name: "stats_statsdsocketlog.h",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) " +
+        "--header $(genDir)/stats_statsdsocketlog.h " +
+        "--module statsdsocket " +
+        "--namespace android,os,statsdsocket",
+    out: [
+        "stats_statsdsocketlog.h",
+    ],
+}
+
+genrule {
+    name: "stats_statsdsocketlog.cpp",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) " +
+        "--cpp $(genDir)/stats_statsdsocketlog.cpp " +
+        "--module statsdsocket " +
+        "--namespace android,os,statsdsocket " +
+        "--importHeader stats_statsdsocketlog.h",
+    out: [
+        "stats_statsdsocketlog.cpp",
+    ],
+}
+
+cc_fuzz {
+    name: "statsevent_fuzzer",
+    defaults: [
+        "libstatssocket_defaults",
+    ],
+    srcs: [
+        "fuzzers/stats_event_fuzzer.cpp",
+    ],
+    local_include_dirs: [
+        "include",
+    ],
+    host_supported: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    fuzz_config: {
+        cc: [
+            "singhtejinder@google.com",
+            "sharaienko@google.com",
+        ],
+    },
 }
diff --git a/lib/libstatssocket/fuzzers/stats_event_fuzzer.cpp b/lib/libstatssocket/fuzzers/stats_event_fuzzer.cpp
new file mode 100644
index 0000000..975ecf7
--- /dev/null
+++ b/lib/libstatssocket/fuzzers/stats_event_fuzzer.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include "include/stats_event.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FuzzedDataProvider fdp(data, size);
+
+    // This only tests the libstatssocket APIs.
+    // Since fuzzing is not supported across processes, it does not fuzz statsd.
+    // See statsd_fuzzer.
+
+    AStatsEvent* event = AStatsEvent_obtain();
+
+    AStatsEvent_setAtomId(event, fdp.ConsumeIntegral<int32_t>());
+    // atom-level annotation
+    AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+
+    while (fdp.remaining_bytes() > 0) {
+        AStatsEvent_writeInt32(event, fdp.ConsumeIntegral<int32_t>());
+        AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+        AStatsEvent_addInt32Annotation(event, fdp.ConsumeIntegral<int32_t>(),
+                                       fdp.ConsumeIntegral<int32_t>());
+        AStatsEvent_writeBool(event, fdp.ConsumeBool());
+        AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+        AStatsEvent_addInt32Annotation(event, fdp.ConsumeIntegral<int32_t>(),
+                                       fdp.ConsumeIntegral<int32_t>());
+        AStatsEvent_writeFloat(event, fdp.ConsumeFloatingPoint<float>());
+        AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+        AStatsEvent_addInt32Annotation(event, fdp.ConsumeIntegral<int32_t>(),
+                                       fdp.ConsumeIntegral<int32_t>());
+        AStatsEvent_writeInt64(event, fdp.ConsumeIntegral<int64_t>());
+        AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+        AStatsEvent_addInt32Annotation(event, fdp.ConsumeIntegral<int32_t>(),
+                                       fdp.ConsumeIntegral<int32_t>());
+        AStatsEvent_writeString(event, fdp.ConsumeRandomLengthString().c_str());
+        AStatsEvent_addBoolAnnotation(event, fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeBool());
+        AStatsEvent_addInt32Annotation(event, fdp.ConsumeIntegral<int32_t>(),
+                                       fdp.ConsumeIntegral<int32_t>());
+    }
+
+    AStatsEvent_write(event);
+    AStatsEvent_release(event);
+    return 0;
+}
diff --git a/lib/libstatssocket/include/stats_annotations.h b/lib/libstatssocket/include/stats_annotations.h
index e812af0..2963db9 100644
--- a/lib/libstatssocket/include/stats_annotations.h
+++ b/lib/libstatssocket/include/stats_annotations.h
@@ -80,6 +80,116 @@
      * Introduced in API 31.
      */
     ASTATSLOG_ANNOTATION_ID_STATE_NESTED = 8,
+
+    /**
+     * Annotation ID constant to indicate the restriction category of an atom.
+     * This annotation must only be attached to the atom id. This is an int annotation.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY = 9,
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains peripheral device info.
+     * This is a bool annotation.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO = 10,
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains app usage information.
+     * This is a bool annotation.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE = 11,
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains app activity information.
+     * This is a bool annotation.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY = 12,
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains health connect information.
+     * This is a bool annotation.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT = 13,
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains accessibility information.
+     * This is a bool annotation.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY = 14,
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains system search information.
+     * This is a bool annotation.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH = 15,
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains user engagement information.
+     * This is a bool annotation.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT = 16,
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains ambient sensing information.
+     * This is a bool annotation.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING = 17,
+
+    /**
+     * Annotation ID to indicate that a field of an atom contains demographic classification
+     * information. This is a bool annotation.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION = 18,
 };
 
+enum AStatsLogRestrictionCategory : uint32_t {
+    /**
+     * Restriction category for atoms about diagnostics.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC = 1,
+
+    /**
+     * Restriction category for atoms about system intelligence.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE = 2,
+
+    /**
+     * Restriction category for atoms about authentication.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_RESTRICTION_CATEGORY_AUTHENTICATION = 3,
+
+    /**
+     * Restriction category for atoms about fraud and abuse.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSLOG_RESTRICTION_CATEGORY_FRAUD_AND_ABUSE = 4,
+
+};
 __END_DECLS
diff --git a/lib/libstatssocket/include/stats_event.h b/lib/libstatssocket/include/stats_event.h
index 23e1419..f131bf5 100644
--- a/lib/libstatssocket/include/stats_event.h
+++ b/lib/libstatssocket/include/stats_event.h
@@ -73,6 +73,10 @@
 
 /**
  * Writes the StatsEvent to the stats log.
+ * For all UIDs except system server:
+ * - Returns number of bytes written into the socket, or socket error code.
+ * For the system_server the write is done via intermediate queue:
+ * - Returns 1 if event was added into the queue, 0 otherwise.
  *
  * After calling this, AStatsEvent_release must be called,
  * and is the only function that can be safely called.
diff --git a/lib/libstatssocket/libstatssocket.map.txt b/lib/libstatssocket/libstatssocket.map.txt
index aa6eb30..f48e159 100644
--- a/lib/libstatssocket/libstatssocket.map.txt
+++ b/lib/libstatssocket/libstatssocket.map.txt
@@ -1,25 +1,25 @@
 LIBSTATSSOCKET {
     global:
-        AStatsEvent_obtain; # apex # introduced=30
-        AStatsEvent_build; # apex # introduced=30
-        AStatsEvent_write; # apex # introduced=30
-        AStatsEvent_release; # apex # introduced=30
-        AStatsEvent_setAtomId; # apex # introduced=30
-        AStatsEvent_writeInt32; # apex # introduced=30
-        AStatsEvent_writeInt64; # apex # introduced=30
-        AStatsEvent_writeFloat; # apex # introduced=30
-        AStatsEvent_writeBool; # apex # introduced=30
-        AStatsEvent_writeByteArray; # apex # introduced=30
-        AStatsEvent_writeString; # apex # introduced=30
-        AStatsEvent_writeAttributionChain; # apex # introduced=30
-        AStatsEvent_writeInt32Array; # apex # introduced=Tiramisu
-        AStatsEvent_writeInt64Array; # apex # introduced=Tiramisu
-        AStatsEvent_writeFloatArray; # apex # introduced=Tiramisu
-        AStatsEvent_writeBoolArray; # apex # introduced=Tiramisu
-        AStatsEvent_writeStringArray; # apex # introduced=Tiramisu
-        AStatsEvent_addBoolAnnotation; # apex # introduced=30
-        AStatsEvent_addInt32Annotation; # apex # introduced=30
-        AStatsSocket_close; # apex # introduced=30
+        AStatsEvent_obtain; # apex introduced=30
+        AStatsEvent_build; # apex introduced=30
+        AStatsEvent_write; # apex introduced=30
+        AStatsEvent_release; # apex introduced=30
+        AStatsEvent_setAtomId; # apex introduced=30
+        AStatsEvent_writeInt32; # apex introduced=30
+        AStatsEvent_writeInt64; # apex introduced=30
+        AStatsEvent_writeFloat; # apex introduced=30
+        AStatsEvent_writeBool; # apex introduced=30
+        AStatsEvent_writeByteArray; # apex introduced=30
+        AStatsEvent_writeString; # apex introduced=30
+        AStatsEvent_writeAttributionChain; # apex introduced=30
+        AStatsEvent_writeInt32Array; # apex introduced=Tiramisu
+        AStatsEvent_writeInt64Array; # apex introduced=Tiramisu
+        AStatsEvent_writeFloatArray; # apex introduced=Tiramisu
+        AStatsEvent_writeBoolArray; # apex introduced=Tiramisu
+        AStatsEvent_writeStringArray; # apex introduced=Tiramisu
+        AStatsEvent_addBoolAnnotation; # apex introduced=30
+        AStatsEvent_addInt32Annotation; # apex introduced=30
+        AStatsSocket_close; # apex introduced=30
     local:
         *;
 };
diff --git a/lib/libstatssocket/stats_buffer_writer.c b/lib/libstatssocket/stats_buffer_writer.c
index 5ad2ec0..33e2ef7 100644
--- a/lib/libstatssocket/stats_buffer_writer.c
+++ b/lib/libstatssocket/stats_buffer_writer.c
@@ -15,9 +15,13 @@
  */
 
 #include "include/stats_buffer_writer.h"
+
 #include <errno.h>
 #include <sys/time.h>
 #include <sys/uio.h>
+
+#include "stats_buffer_writer_impl.h"
+#include "stats_buffer_writer_queue.h"
 #include "statsd_writer.h"
 
 static const uint32_t kStatsEventTag = 1937006964;
@@ -27,6 +31,12 @@
 static int __write_to_statsd_init(struct iovec* vec, size_t nr);
 static int (*__write_to_statsd)(struct iovec* vec, size_t nr) = __write_to_statsd_init;
 
+/**
+ * @brief Logs the error code associated with atom loss
+ *
+ * @param error To distinguish source of error, the errno code values must be negative,
+ *              while the libstatssocket internal error codes are positive
+ */
 void note_log_drop(int error, int atomId) {
     statsdLoggerWrite.noteDrop(error, atomId);
 }
@@ -45,6 +55,19 @@
 }
 
 int write_buffer_to_statsd(void* buffer, size_t size, uint32_t atomId) {
+    const int kQueueOverflowErrorCode = 1;
+    if (should_write_via_queue(atomId)) {
+        const bool ret = write_buffer_to_statsd_queue(buffer, size, atomId);
+        if (!ret) {
+            // to account on the loss, note atom drop with predefined internal error code
+            note_log_drop(kQueueOverflowErrorCode, atomId);
+        }
+        return ret;
+    }
+    return write_buffer_to_statsd_impl(buffer, size, atomId, true);
+}
+
+int write_buffer_to_statsd_impl(void* buffer, size_t size, uint32_t atomId, bool doNoteDrop) {
     int ret = 1;
 
     struct iovec vecs[2];
@@ -55,7 +78,7 @@
 
     ret = __write_to_statsd(vecs, 2);
 
-    if (ret < 0) {
+    if (ret < 0 && doNoteDrop) {
         note_log_drop(ret, atomId);
     }
 
diff --git a/lib/libstatssocket/stats_buffer_writer_impl.h b/lib/libstatssocket/stats_buffer_writer_impl.h
new file mode 100644
index 0000000..a9b8a48
--- /dev/null
+++ b/lib/libstatssocket/stats_buffer_writer_impl.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+int write_buffer_to_statsd_impl(void* buffer, size_t size, uint32_t atomId, bool doNoteDrop);
+
+__END_DECLS
diff --git a/lib/libstatssocket/stats_buffer_writer_queue.cpp b/lib/libstatssocket/stats_buffer_writer_queue.cpp
new file mode 100644
index 0000000..cf21c4e
--- /dev/null
+++ b/lib/libstatssocket/stats_buffer_writer_queue.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stats_buffer_writer_queue.h"
+
+#include <private/android_filesystem_config.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <queue>
+#include <thread>
+
+#include "stats_buffer_writer_impl.h"
+#include "stats_buffer_writer_queue_impl.h"
+#include "utils.h"
+
+BufferWriterQueue::BufferWriterQueue() : mWorkThread(&BufferWriterQueue::processCommands, this) {
+}
+
+BufferWriterQueue::~BufferWriterQueue() {
+    terminate();
+    // at this stage there can be N elements in the queue for which memory needs to be freed
+    // explicitly
+    drainQueue();
+}
+
+bool BufferWriterQueue::write(const uint8_t* buffer, size_t size, uint32_t atomId) {
+    Cmd cmd = createWriteBufferCmd(buffer, size, atomId);
+    if (cmd.buffer == NULL) {
+        return false;
+    }
+    return pushToQueue(cmd);
+}
+
+size_t BufferWriterQueue::getQueueSize() const {
+    std::unique_lock<std::mutex> lock(mMutex);
+    return mCmdQueue.size();
+}
+
+bool BufferWriterQueue::pushToQueue(const Cmd& cmd) {
+    {
+        std::unique_lock<std::mutex> lock(mMutex);
+        if (mCmdQueue.size() >= kQueueMaxSizeLimit) {
+            // TODO (b/258003151): add logging info about internal queue overflow with appropriate
+            // error code
+            return false;
+        }
+        mCmdQueue.push(cmd);
+    }
+    mCondition.notify_one();
+    return true;
+}
+
+BufferWriterQueue::Cmd BufferWriterQueue::createWriteBufferCmd(const uint8_t* buffer, size_t size,
+                                                               uint32_t atomId) {
+    BufferWriterQueue::Cmd writeCmd;
+    writeCmd.atomId = atomId;
+    writeCmd.buffer = (uint8_t*)malloc(size);
+    if (writeCmd.buffer == NULL) {
+        return writeCmd;
+    }
+    memcpy(writeCmd.buffer, buffer, size);
+    writeCmd.size = size;
+    return writeCmd;
+}
+
+void BufferWriterQueue::terminate() {
+    if (mWorkThread.joinable()) {
+        mDoTerminate = true;
+        Cmd terminateCmd;
+        terminateCmd.buffer = NULL;
+        pushToQueue(terminateCmd);
+        mWorkThread.join();
+    }
+}
+
+void BufferWriterQueue::drainQueue() {
+    std::unique_lock<std::mutex> lock(mMutex);
+    while (!mCmdQueue.empty()) {
+        free(mCmdQueue.front().buffer);
+        mCmdQueue.pop();
+    }
+}
+
+void BufferWriterQueue::processCommands() {
+    while (true) {
+        // temporary local thread copy
+        Cmd cmd;
+        {
+            std::unique_lock<std::mutex> lock(mMutex);
+            if (mCmdQueue.empty()) {
+                mCondition.wait(lock, [this] { return !this->mCmdQueue.empty(); });
+            }
+            cmd = mCmdQueue.front();
+        }
+
+        if (cmd.buffer == NULL) {
+            // null buffer ptr used as a marker of the termination request
+            return;
+        }
+
+        const bool writeSuccess = handleCommand(cmd);
+        if (writeSuccess) {
+            // no event drop is observed otherwise command remains in the queue
+            // and worker thread will try to log later on
+
+            // call free() explicitly here to free memory before the mutex lock
+            free(cmd.buffer);
+            {
+                std::unique_lock<std::mutex> lock(mMutex);
+                // this will lead to Cmd destructor call which will be no-op since now the
+                // buffer is NULL
+                mCmdQueue.pop();
+            }
+        }
+        // TODO (b/258003151): add logging info about retry count
+
+        if (mDoTerminate) {
+            return;
+        }
+
+        // attempt to enforce the logging frequency constraints
+        // in case of failed write due to socket overflow the sleep can be longer
+        // to not overload socket continuously
+        if (!writeSuccess) {
+            std::this_thread::sleep_for(std::chrono::milliseconds(kDelayOnFailedWriteMs));
+        }
+    }
+}
+
+bool BufferWriterQueue::handleCommand(const Cmd& cmd) const {
+    // skip log drop if occurs, since the atom remains in the queue and write will be retried
+    return write_buffer_to_statsd_impl(cmd.buffer, cmd.size, cmd.atomId, /*doNoteDrop*/ false) > 0;
+}
+
+bool write_buffer_to_statsd_queue(const uint8_t* buffer, size_t size, uint32_t atomId) {
+    static BufferWriterQueue queue;
+    return queue.write(buffer, size, atomId);
+}
+
+#ifdef ENABLE_BENCHMARK_SUPPORT
+bool should_write_via_queue(uint32_t atomId) {
+#else
+bool should_write_via_queue(uint32_t /*atomId*/) {
+#endif
+    const uint32_t appUid = getuid();
+
+    // hard-coded push all system server atoms to queue
+    if (appUid == AID_SYSTEM) {
+        return true;
+    }
+
+#ifdef ENABLE_BENCHMARK_SUPPORT
+    // some hand-picked atoms to be pushed into the queue
+    switch (atomId) {
+        case 47:  // APP_BREADCRUMB_REPORTED for statsd_benchmark purpose
+            return true;
+        default:
+            return false;
+    }
+#endif  // ENABLE_BENCHMARK_SUPPORT
+    return false;
+}
diff --git a/lib/libstatssocket/stats_buffer_writer_queue.h b/lib/libstatssocket/stats_buffer_writer_queue.h
new file mode 100644
index 0000000..41afcc7
--- /dev/null
+++ b/lib/libstatssocket/stats_buffer_writer_queue.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+bool write_buffer_to_statsd_queue(const uint8_t* buffer, size_t size, uint32_t atomId);
+
+bool should_write_via_queue(uint32_t atomId);
+
+__END_DECLS
diff --git a/lib/libstatssocket/stats_buffer_writer_queue_impl.h b/lib/libstatssocket/stats_buffer_writer_queue_impl.h
new file mode 100644
index 0000000..a5c6873
--- /dev/null
+++ b/lib/libstatssocket/stats_buffer_writer_queue_impl.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <queue>
+#include <thread>
+
+class BufferWriterQueue {
+public:
+    constexpr static int kDelayOnFailedWriteMs = 5;
+    constexpr static int kQueueMaxSizeLimit = 4800;  // 2X max_dgram_qlen
+
+    BufferWriterQueue();
+    virtual ~BufferWriterQueue();
+
+    bool write(const uint8_t* buffer, size_t size, uint32_t atomId);
+
+    size_t getQueueSize() const;
+
+    void drainQueue();
+
+    struct Cmd {
+        uint8_t* buffer = NULL;
+        int atomId = 0;
+        int size = 0;
+    };
+
+    virtual bool handleCommand(const Cmd& cmd) const;
+
+private:
+    std::condition_variable mCondition;
+    mutable std::mutex mMutex;
+    std::queue<Cmd> mCmdQueue;
+    std::atomic_bool mDoTerminate = false;
+    std::thread mWorkThread;
+
+    static Cmd createWriteBufferCmd(const uint8_t* buffer, size_t size, uint32_t atomId);
+
+    bool pushToQueue(const Cmd& cmd);
+
+    void terminate();
+
+    void processCommands();
+};
diff --git a/lib/libstatssocket/stats_event.c b/lib/libstatssocket/stats_event.c
index 9bb4c52..ade1b93 100644
--- a/lib/libstatssocket/stats_event.c
+++ b/lib/libstatssocket/stats_event.c
@@ -15,10 +15,12 @@
  */
 
 #include "include/stats_event.h"
+
 #include <stdlib.h>
 #include <string.h>
-#include <time.h>
+
 #include "stats_buffer_writer.h"
+#include "utils.h"
 
 #define LOGGER_ENTRY_MAX_PAYLOAD 4068
 // Max payload size is 4 bytes less as 4 bytes are reserved for stats_eventTag.
@@ -81,13 +83,6 @@
     size_t bufSize;
 };
 
-static int64_t get_elapsed_realtime_ns() {
-    struct timespec t;
-    t.tv_sec = t.tv_nsec = 0;
-    clock_gettime(CLOCK_BOOTTIME, &t);
-    return (int64_t)t.tv_sec * 1000000000LL + t.tv_nsec;
-}
-
 AStatsEvent* AStatsEvent_obtain() {
     AStatsEvent* event = malloc(sizeof(AStatsEvent));
     event->lastFieldPos = 0;
diff --git a/lib/libstatssocket/stats_socket_loss_reporter.cpp b/lib/libstatssocket/stats_socket_loss_reporter.cpp
new file mode 100644
index 0000000..5e2d272
--- /dev/null
+++ b/lib/libstatssocket/stats_socket_loss_reporter.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stats_event.h>
+#include <stats_socket_loss_reporter.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "stats_statsdsocketlog.h"
+#include "utils.h"
+
+StatsSocketLossReporter::StatsSocketLossReporter() : mUid(getuid()) {
+}
+
+StatsSocketLossReporter::~StatsSocketLossReporter() {
+    // try to dump loss stats since there might be pending data which have been not sent earlier
+    // due to:
+    // - cool down timer was active
+    // - no input atoms to trigger loss info dump after cooldown timer expired
+    dumpAtomsLossStats(true);
+}
+
+StatsSocketLossReporter& StatsSocketLossReporter::getInstance() {
+    static StatsSocketLossReporter instance;
+    return instance;
+}
+
+void StatsSocketLossReporter::noteDrop(int32_t error, int32_t atomId) {
+    using namespace android::os::statsdsocket;
+
+    const int64_t currentRealtimeTsNanos = get_elapsed_realtime_ns();
+
+    // The intention is to skip self counting, however the timestamps still need to be updated
+    // to know when was last failed attempt to log atom.
+    // This is required for more accurate cool down timer work
+    if (mFirstTsNanos == 0) {
+        mFirstTsNanos.store(currentRealtimeTsNanos, std::memory_order_relaxed);
+    }
+    mLastTsNanos.store(currentRealtimeTsNanos, std::memory_order_relaxed);
+
+    if (atomId == STATS_SOCKET_LOSS_REPORTED) {
+        // avoid self counting due to write to socket might fail during dumpAtomsLossStats()
+        // also due to mutex is not re-entrant and is already locked by dumpAtomsLossStats() API,
+        // return to avoid deadlock
+        // alternative is to consider std::recursive_mutex
+        return;
+    }
+
+    std::unique_lock<std::mutex> lock(mMutex);
+
+    // using unordered_map is more CPU efficient vs vectors, however will require some
+    // postprocessing before writing into the socket
+    const LossInfoKey key = std::make_pair(error, atomId);
+    auto counterIt = mLossInfo.find(key);
+    if (counterIt != mLossInfo.end()) {
+        ++counterIt->second;
+    } else if (mLossInfo.size() < kMaxAtomTagsCount) {
+        mLossInfo[key] = 1;
+    } else {
+        mOverflowCounter++;
+    }
+}
+
+void StatsSocketLossReporter::dumpAtomsLossStats(bool forceDump) {
+    using namespace android::os::statsdsocket;
+
+    const int64_t currentRealtimeTsNanos = get_elapsed_realtime_ns();
+
+    if (!forceDump && isCooldownTimerActive(currentRealtimeTsNanos)) {
+        // To avoid socket flooding with more STATS_SOCKET_LOSS_REPORTED atoms,
+        // which have high probability of write failures, the cooldown timer approach is applied:
+        // - start cooldown timer for 10us for every failed dump
+        // - before writing STATS_SOCKET_LOSS_REPORTED do check the timestamp to keep some delay
+        return;
+    }
+
+    // intention to hold mutex here during the stats_write() to avoid data copy overhead
+    std::unique_lock<std::mutex> lock(mMutex);
+    if (mLossInfo.size() == 0) {
+        return;
+    }
+
+    // populate temp vectors to be written into the socket
+    std::vector<int> errors(mLossInfo.size());
+    std::vector<int> tags(mLossInfo.size());
+    std::vector<int> counts(mLossInfo.size());
+
+    auto lossInfoIt = mLossInfo.begin();
+    for (size_t i = 0; i < mLossInfo.size(); i++, lossInfoIt++) {
+        const LossInfoKey& key = lossInfoIt->first;
+        errors[i] = key.first;
+        tags[i] = key.second;
+        counts[i] = lossInfoIt->second;
+    }
+
+    // below call might lead to socket loss event - intention is to avoid self counting
+    const int ret = stats_write(STATS_SOCKET_LOSS_REPORTED, mUid, mFirstTsNanos, mLastTsNanos,
+                                mOverflowCounter, errors, tags, counts);
+    if (ret > 0) {
+        // Otherwise, in case of failure we preserve all socket loss information between dumps.
+        // When above write failed - the socket loss stats are not discarded
+        // and would be re-send during next attempt.
+        mOverflowCounter = 0;
+        mLossInfo.clear();
+
+        mFirstTsNanos.store(0, std::memory_order_relaxed);
+        mLastTsNanos.store(0, std::memory_order_relaxed);
+    }
+    // since the delay before next attempt is significantly larger than this API call
+    // duration it is ok to have correctness of timestamp in a range of 10us
+    startCooldownTimer(currentRealtimeTsNanos);
+}
+
+void StatsSocketLossReporter::startCooldownTimer(int64_t elapsedRealtimeNanos) {
+    mCooldownTimerFinishAtNanos = elapsedRealtimeNanos + kCoolDownTimerDurationNanos;
+}
+
+bool StatsSocketLossReporter::isCooldownTimerActive(int64_t elapsedRealtimeNanos) const {
+    return mCooldownTimerFinishAtNanos > elapsedRealtimeNanos;
+}
diff --git a/lib/libstatssocket/stats_socket_loss_reporter.h b/lib/libstatssocket/stats_socket_loss_reporter.h
new file mode 100644
index 0000000..b857321
--- /dev/null
+++ b/lib/libstatssocket/stats_socket_loss_reporter.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <atomic>
+#include <thread>
+#include <unordered_map>
+
+class StatsSocketLossReporter {
+public:
+    static StatsSocketLossReporter& getInstance();
+
+    void noteDrop(int32_t error, int32_t atomId);
+
+    /**
+     * @brief Dump loss info into statsd as a STATS_SOCKET_LOSS_REPORTED atom instance
+     *
+     * @param forceDump skip cooldown timer evaluation
+     * @return true if atom have been written into the socket successfully
+     * @return false if atom have been written into the socket with an error
+     */
+    void dumpAtomsLossStats(bool forceDump = false);
+
+    ~StatsSocketLossReporter();
+
+private:
+    StatsSocketLossReporter();
+
+    void startCooldownTimer(int64_t elapsedRealtimeNanos);
+    bool isCooldownTimerActive(int64_t elapsedRealtimeNanos) const;
+
+    const int32_t mUid;
+    std::atomic_int64_t mFirstTsNanos = 0;
+    std::atomic_int64_t mLastTsNanos = 0;
+    std::atomic_int64_t mCooldownTimerFinishAtNanos = 0;
+
+    // Loss info data will be logged to statsd as a regular AStatsEvent
+    // which means it needs to obey event size limitations (4kB)
+    // for N tag ids the loss info might take N * 12 + 8 + 8 + 4 bytes
+    // defining guardrail as a 100 tag ids should limit the atom size to
+    // 100 * 12 + 8 + 8 + 4 ~ 1.2kB
+    const size_t kMaxAtomTagsCount = 100;
+
+    const int64_t kCoolDownTimerDurationNanos = 10 * 1000 * 1000;  // 10ms
+
+    struct HashPair final {
+        template <class TFirst, class TSecond>
+        size_t operator()(const std::pair<TFirst, TSecond>& p) const noexcept {
+            uintmax_t hash = std::hash<TFirst>{}(p.first);
+            hash <<= sizeof(uintmax_t) * 4;
+            hash ^= std::hash<TSecond>{}(p.second);
+            return std::hash<uintmax_t>{}(hash);
+        }
+    };
+
+    // guards access to below mLossInfo
+    mutable std::mutex mMutex;
+
+    using LossInfoKey = std::pair<int, int>;  // [error, tag]
+
+    // Represents loss info as a counter per [error, tag] pair
+    std::unordered_map<LossInfoKey, int, HashPair> mLossInfo;
+
+    // tracks guardrail kMaxAtomTagsCount hit count
+    int32_t mOverflowCounter = 0;
+};
diff --git a/lib/libstatssocket/statsd_writer.cpp b/lib/libstatssocket/statsd_writer.cpp
index a3c2dd6..4133e26 100644
--- a/lib/libstatssocket/statsd_writer.cpp
+++ b/lib/libstatssocket/statsd_writer.cpp
@@ -33,6 +33,8 @@
 #include <time.h>
 #include <unistd.h>
 
+#include "stats_socket_loss_reporter.h"
+#include "utils.h"
 
 // Compatibility shims for glibc-2.17 in the Android tree.
 #ifndef __BIONIC__
@@ -105,7 +107,7 @@
         if (sock < 0) {
             ret = -errno;
         } else {
-            int sndbuf = 1 * 1024 * 1024;  // set max send buffer size 1MB
+            const int sndbuf = 2 * 1024 * 1024;  // set max send buffer size 2MB
             socklen_t bufLen = sizeof(sndbuf);
             // SO_RCVBUF does not have an effect on unix domain socket, but SO_SNDBUF does.
             // Proceed to connect even setsockopt fails.
@@ -166,6 +168,8 @@
     atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);
     atomic_exchange_explicit(&log_error, error, memory_order_relaxed);
     atomic_exchange_explicit(&atom_tag, tag, memory_order_relaxed);
+
+    StatsSocketLossReporter::getInstance().noteDrop(error, tag);
 }
 
 static int statsdIsClosed() {
@@ -239,6 +243,10 @@
             ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
             if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
                 atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
+            } else {
+                // try to send socket loss info only when socket connection established
+                // and it is proved by previous write that socket is available
+                StatsSocketLossReporter::getInstance().dumpAtomsLossStats();
             }
         }
     }
diff --git a/lib/libstatssocket/tests/stats_buffer_writer_queue_test.cpp b/lib/libstatssocket/tests/stats_buffer_writer_queue_test.cpp
new file mode 100644
index 0000000..008009b
--- /dev/null
+++ b/lib/libstatssocket/tests/stats_buffer_writer_queue_test.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+
+#include "stats_buffer_writer_queue_impl.h"
+#include "stats_event.h"
+#include "utils.h"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::DoAll;
+using testing::Return;
+using testing::StrictMock;
+
+namespace {
+
+constexpr static int WAIT_MS = 100;
+
+static AStatsEvent* generateTestEvent() {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32(event, 5);
+    AStatsEvent_write(event);
+    return event;
+}
+
+class BasicBufferWriterQueueMock : public BufferWriterQueue {
+public:
+    BasicBufferWriterQueueMock() = default;
+    MOCK_METHOD(bool, handleCommand, (const BufferWriterQueue::Cmd& cmd), (const override));
+};
+
+typedef StrictMock<BasicBufferWriterQueueMock> BufferWriterQueueMock;
+
+}  // namespace
+
+TEST(StatsBufferWriterQueueTest, TestWriteSuccess) {
+    AStatsEvent* event = generateTestEvent();
+
+    size_t eventBufferSize = 0;
+    const uint8_t* buffer = AStatsEvent_getBuffer(event, &eventBufferSize);
+    EXPECT_GE(eventBufferSize, 0);
+    EXPECT_TRUE(buffer != nullptr);
+
+    const uint32_t atomId = AStatsEvent_getAtomId(event);
+
+    BufferWriterQueueMock queue;
+    EXPECT_CALL(queue, handleCommand(_)).WillOnce(Return(true));
+    // simulate failed write to stats socket
+    const bool addedToQueue = queue.write(buffer, eventBufferSize, atomId);
+    AStatsEvent_release(event);
+    EXPECT_TRUE(addedToQueue);
+    // to yeld to the queue worker thread
+    std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+    queue.drainQueue();
+    EXPECT_EQ(queue.getQueueSize(), 0);
+}
+
+TEST(StatsBufferWriterQueueTest, TestWriteOverflow) {
+    AStatsEvent* event = generateTestEvent();
+
+    size_t eventBufferSize = 0;
+    const uint8_t* buffer = AStatsEvent_getBuffer(event, &eventBufferSize);
+    EXPECT_GE(eventBufferSize, 0);
+    EXPECT_TRUE(buffer != nullptr);
+
+    const uint32_t atomId = AStatsEvent_getAtomId(event);
+
+    BufferWriterQueueMock queue;
+    EXPECT_CALL(queue, handleCommand(_)).WillRepeatedly(Return(false));
+    // simulate failed write to stats socket
+    for (int i = 0; i < BufferWriterQueueMock::kQueueMaxSizeLimit; i++) {
+        const bool addedToQueue = queue.write(buffer, eventBufferSize, atomId);
+        EXPECT_TRUE(addedToQueue);
+    }
+
+    const bool addedToQueue = queue.write(buffer, eventBufferSize, atomId);
+    AStatsEvent_release(event);
+    EXPECT_FALSE(addedToQueue);
+
+    EXPECT_EQ(queue.getQueueSize(), BufferWriterQueueMock::kQueueMaxSizeLimit);
+
+    queue.drainQueue();
+    EXPECT_EQ(queue.getQueueSize(), 0);
+}
+
+TEST(StatsBufferWriterQueueTest, TestSleepOnOverflow) {
+    AStatsEvent* event = generateTestEvent();
+
+    size_t eventBufferSize = 0;
+    const uint8_t* buffer = AStatsEvent_getBuffer(event, &eventBufferSize);
+    EXPECT_GE(eventBufferSize, 0);
+    EXPECT_TRUE(buffer != nullptr);
+
+    const uint32_t atomId = AStatsEvent_getAtomId(event);
+
+    std::vector<int64_t> attemptsTs;
+
+    BufferWriterQueueMock queue;
+    ON_CALL(queue, handleCommand(_))
+            .WillByDefault(DoAll(
+                    [&attemptsTs](const BufferWriterQueue::Cmd&) {
+                        // store timestamp for command handler invocations
+                        attemptsTs.push_back(get_elapsed_realtime_ns());
+                        return false;
+                    },
+                    Return(false)));
+
+    EXPECT_CALL(queue, handleCommand(_)).Times(AnyNumber());
+
+    // simulate failed write to stats socket to fill the queue
+    for (int i = 0; i < BufferWriterQueueMock::kQueueMaxSizeLimit; i++) {
+        const bool addedToQueue = queue.write(buffer, eventBufferSize, atomId);
+        EXPECT_TRUE(addedToQueue);
+    }
+    AStatsEvent_release(event);
+    // to yeld to the queue worker thread
+    std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+    // to eliminate extra commands handling on the worker thread
+    queue.drainQueue();
+    EXPECT_EQ(queue.getQueueSize(), 0);
+
+    EXPECT_GE(attemptsTs.size(), 2);
+    for (int i = 0; i < attemptsTs.size() - 1; i++) {
+        EXPECT_GE(attemptsTs[i + 1] - attemptsTs[i],
+                  BufferWriterQueueMock::kDelayOnFailedWriteMs * 1000000);
+    }
+}
+
+TEST(StatsBufferWriterQueueTest, TestTerminateNonEmptyQueue) {
+    AStatsEvent* event = generateTestEvent();
+
+    size_t eventBufferSize = 0;
+    const uint8_t* buffer = AStatsEvent_getBuffer(event, &eventBufferSize);
+    EXPECT_GE(eventBufferSize, 0);
+    EXPECT_TRUE(buffer != nullptr);
+
+    const uint32_t atomId = AStatsEvent_getAtomId(event);
+
+    BufferWriterQueueMock queue;
+    EXPECT_CALL(queue, handleCommand(_)).WillRepeatedly(Return(false));
+    // simulate failed write to stats socket
+    for (int i = 0; i < BufferWriterQueueMock::kQueueMaxSizeLimit; i++) {
+        const bool addedToQueue = queue.write(buffer, eventBufferSize, atomId);
+        EXPECT_TRUE(addedToQueue);
+    }
+    AStatsEvent_release(event);
+    EXPECT_EQ(queue.getQueueSize(), BufferWriterQueueMock::kQueueMaxSizeLimit);
+    queue.drainQueue();
+    EXPECT_EQ(queue.getQueueSize(), 0);
+}
diff --git a/lib/libstatssocket/tests/stats_socketlog_test.cpp b/lib/libstatssocket/tests/stats_socketlog_test.cpp
new file mode 100644
index 0000000..b4bf723
--- /dev/null
+++ b/lib/libstatssocket/tests/stats_socketlog_test.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "stats_statsdsocketlog.h"
+#include "utils.h"
+
+TEST(StatsStatsdSocketLog, TestToSocketLossError) {
+    using namespace android::os::statsdsocket;
+
+    EXPECT_EQ(-EAGAIN, toSocketLossError(EAGAIN));
+    EXPECT_EQ(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_UNKNOWN,
+              toSocketLossError(ERANGE));
+}
diff --git a/lib/libstatssocket/utils.cpp b/lib/libstatssocket/utils.cpp
new file mode 100644
index 0000000..8f8775f
--- /dev/null
+++ b/lib/libstatssocket/utils.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+
+#include <errno.h>
+#include <time.h>
+
+#include "stats_statsdsocketlog.h"
+
+int64_t get_elapsed_realtime_ns() {
+    struct timespec t;
+    t.tv_sec = t.tv_nsec = 0;
+    clock_gettime(CLOCK_BOOTTIME, &t);
+    return (int64_t)t.tv_sec * 1000000000LL + t.tv_nsec;
+}
+
+int toSocketLossError(int errno_code) {
+    using namespace android::os::statsdsocket;
+
+    // compile time checks
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EPERM == -EPERM,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EINTR == -EINTR,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EIO == -EIO,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EBADF == -EBADF,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(
+            STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EAGAIN == -EAGAIN,
+            "Socket Loss Error codes mapping function needs to be updated");  // same as EWOULDBLOCK
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EFAULT == -EFAULT,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_ENODEV == -ENODEV,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EINVAL == -EINVAL,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EFBIG == -EFBIG,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_ENOSPC == -ENOSPC,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EPIPE == -EPIPE,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EDESTADDRREQ ==
+                          -EDESTADDRREQ,
+                  "Socket Loss Error codes mapping function needs to be updated");
+    static_assert(STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_ON_WRITE_EDQUOT == -EDQUOT,
+                  "Socket Loss Error codes mapping function needs to be updated");
+
+    switch (errno_code) {
+        case EPERM:
+        case EINTR:
+        case EIO:
+        case EBADF:
+        case EAGAIN:
+        case EFAULT:
+        case ENODEV:
+        case EINVAL:
+        case EFBIG:
+        case ENOSPC:
+        case EPIPE:
+        case EDESTADDRREQ:
+        case EDQUOT:
+            return -errno_code;
+        default:
+            return STATS_SOCKET_LOSS_REPORTED__ERRORS__SOCKET_LOSS_ERROR_UNKNOWN;
+    }
+}
diff --git a/lib/libstatssocket/utils.h b/lib/libstatssocket/utils.h
new file mode 100644
index 0000000..1eada99
--- /dev/null
+++ b/lib/libstatssocket/utils.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+int64_t get_elapsed_realtime_ns();
+
+int toSocketLossError(int errno_code);
+
+__END_DECLS
diff --git a/perfetto/README.md b/perfetto/README.md
new file mode 100644
index 0000000..765b743
--- /dev/null
+++ b/perfetto/README.md
@@ -0,0 +1,16 @@
+To push config to be used at device boot automatically
+
+```
+adb push perfetto/atrace.pbtxt /data/misc/perfetto-configs/boottrace.pbtxt
+adb shell setprop persist.debug.perfetto.boottrace 1
+```
+
+The output trace will be written at /data/misc/perfetto-traces/boottrace.perfetto-trace.
+The file will be removed before a new trace is started.
+
+```
+adb pull /data/misc/perfetto-traces/boottrace.perfetto-trace
+```
+
+# Links
+- https://perfetto.dev/docs/case-studies/android-boot-tracing
diff --git a/perfetto/atrace.pbtxt b/perfetto/atrace.pbtxt
new file mode 100644
index 0000000..e5c512c
--- /dev/null
+++ b/perfetto/atrace.pbtxt
@@ -0,0 +1,30 @@
+buffers: {
+    size_kb: 522240
+    fill_policy: DISCARD
+}
+buffers: {
+  size_kb: 8192
+  fill_policy: RING_BUFFER
+}
+data_sources: {
+    config {
+        name: "linux.process_stats"
+        target_buffer: 1
+        process_stats_config {
+            scan_all_processes_on_start: true
+        }
+    }
+}
+data_sources: {
+    config {
+        name: "linux.ftrace"
+        ftrace_config {
+            atrace_categories: "binder_driver"
+            atrace_categories: "binder_lock"
+            atrace_categories: "sm"
+            atrace_categories: "ss"
+            atrace_apps: "*"
+        }
+    }
+}
+duration_ms: 180000
diff --git a/service/java/com/android/server/stats/StatsCompanion.java b/service/java/com/android/server/stats/StatsCompanion.java
index dc477a5..d0954ea 100644
--- a/service/java/com/android/server/stats/StatsCompanion.java
+++ b/service/java/com/android/server/stats/StatsCompanion.java
@@ -112,6 +112,7 @@
         private static final int CODE_DATA_BROADCAST = 1;
         private static final int CODE_ACTIVE_CONFIGS_BROADCAST = 1;
         private static final int CODE_SUBSCRIBER_BROADCAST = 1;
+        private static final int CODE_RESTRICTED_METRICS_BROADCAST = 1;
 
         private final PendingIntent mPendingIntent;
         private final Context mContext;
@@ -184,5 +185,24 @@
                                 + "; presumably it had been cancelled.");
             }
         }
+
+        @Override
+        public void sendRestrictedMetricsChangedBroadcast(long[] metricIds) {
+            enforceStatsdCallingUid();
+            Intent intent = new Intent();
+            intent.putExtra(StatsManager.EXTRA_STATS_RESTRICTED_METRIC_IDS, metricIds);
+            try {
+                mPendingIntent.send(mContext, CODE_RESTRICTED_METRICS_BROADCAST, intent, null,
+                        null);
+                if (DEBUG) {
+                    Log.d(TAG,
+                            "Sent restricted metrics broadcast with metric ids " + Arrays.toString(
+                                    metricIds));
+                }
+            } catch (PendingIntent.CanceledException e) {
+                Log.w(TAG,
+                        "Unable to send restricted metrics changed broadcast using PendingIntent");
+            }
+        }
     }
 }
diff --git a/service/java/com/android/server/stats/StatsCompanionService.java b/service/java/com/android/server/stats/StatsCompanionService.java
index 360889b..6e920c5 100644
--- a/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/service/java/com/android/server/stats/StatsCompanionService.java
@@ -742,18 +742,21 @@
             filter.addAction(Intent.ACTION_PACKAGE_ADDED);
             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
             filter.addDataScheme("package");
-            mContext.registerReceiverForAllUsers(appUpdateReceiver, filter, null, null);
+            mContext.registerReceiverForAllUsers(appUpdateReceiver, filter, null,
+                    /* scheduler= */ mHandler);
 
             // Setup receiver for user initialize (which happens once for a new user)
             // and if a user is removed.
             filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE);
             filter.addAction(Intent.ACTION_USER_REMOVED);
-            mContext.registerReceiverForAllUsers(userUpdateReceiver, filter, null, null);
+            mContext.registerReceiverForAllUsers(userUpdateReceiver, filter, null,
+                    /* scheduler= */ mHandler);
 
             // Setup receiver for device reboots or shutdowns.
             filter = new IntentFilter(Intent.ACTION_REBOOT);
             filter.addAction(Intent.ACTION_SHUTDOWN);
-            mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null, null);
+            mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null,
+                    /* scheduler= */ mHandler);
 
             // Register listener for statsd_java properties updates.
             DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_STATSD_JAVA,
diff --git a/service/java/com/android/server/stats/StatsManagerService.java b/service/java/com/android/server/stats/StatsManagerService.java
index d55a12a..a25c91f 100644
--- a/service/java/com/android/server/stats/StatsManagerService.java
+++ b/service/java/com/android/server/stats/StatsManagerService.java
@@ -26,6 +26,7 @@
 import android.os.Binder;
 import android.os.IPullAtomCallback;
 import android.os.IStatsManagerService;
+import android.os.IStatsQueryCallback;
 import android.os.IStatsd;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
@@ -38,6 +39,7 @@
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -72,6 +74,9 @@
     @GuardedBy("mLock")
     private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap =
             new ArrayMap<>();
+    @GuardedBy("mLock")
+    private ArrayMap<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>>
+            mRestrictedMetricsPirMap = new ArrayMap<>();
 
     public StatsManagerService(Context context) {
         super();
@@ -110,6 +115,39 @@
         }
     }
 
+    private static class ConfigKeyWithPackage {
+        private final String mConfigPackage;
+        private final long mConfigId;
+
+        ConfigKeyWithPackage(String configPackage, long configId) {
+            mConfigPackage = configPackage;
+            mConfigId = configId;
+        }
+
+        public String getConfigPackage() {
+            return mConfigPackage;
+        }
+
+        public long getConfigId() {
+            return mConfigId;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mConfigPackage, mConfigId);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ConfigKeyWithPackage) {
+                ConfigKeyWithPackage other = (ConfigKeyWithPackage) obj;
+                return this.mConfigPackage.equals(other.getConfigPackage())
+                        && this.mConfigId == other.getConfigId();
+            }
+            return false;
+        }
+    }
+
     private static class PullerKey {
         private final int mUid;
         private final int mAtomTag;
@@ -443,23 +481,27 @@
     @Override
     public void getDataFd(long key, String packageName, ParcelFileDescriptor writeFd)
             throws IllegalStateException, RemoteException {
-        enforceDumpAndUsageStatsPermission(packageName);
-        PowerManager powerManager = mContext.getSystemService(PowerManager.class);
-        PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
-                /*tag=*/ StatsManagerService.class.getCanonicalName());
-        final int callingUid = Binder.getCallingUid();
-        final long token = Binder.clearCallingIdentity();
-        wl.acquire();
-        try {
-            IStatsd statsd = waitForStatsd();
-            if (statsd != null) {
-                // create another intermediate pipe for statsd
-                getDataWithFd(statsd, key, callingUid, writeFd);
-                return;
+        try (writeFd) {
+            enforceDumpAndUsageStatsPermission(packageName);
+            PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+            PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                    /*tag=*/ StatsManagerService.class.getCanonicalName());
+            final int callingUid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            wl.acquire();
+            try {
+                IStatsd statsd = waitForStatsd();
+                if (statsd != null) {
+                    // will create another intermediate pipe for statsd
+                    getDataFdFromStatsd(statsd, key, callingUid, writeFd.getFileDescriptor());
+                    return;
+                }
+            } finally {
+                wl.release();
+                Binder.restoreCallingIdentity(token);
             }
-        } finally {
-            wl.release();
-            Binder.restoreCallingIdentity(token);
+        } catch (IOException e) {
+            throw new IllegalStateException("IOException during getDataFd() call", e);
         }
         throw new IllegalStateException("Failed to connect to statsd to getDataFd");
     }
@@ -506,10 +548,97 @@
         throw new IllegalStateException("Failed to connect to statsd to removeConfig");
     }
 
+    @Override
+    public long[] setRestrictedMetricsChangedOperation(PendingIntent pendingIntent,
+            long configId, String configPackage) {
+        enforceRestrictedStatsPermission();
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext);
+        ConfigKeyWithPackage key = new ConfigKeyWithPackage(configPackage, configId);
+        // Add the PIR to a map so we can re-register if statsd is unavailable.
+        synchronized (mLock) {
+            ArrayMap<Integer, PendingIntentRef> innerMap = mRestrictedMetricsPirMap.getOrDefault(
+                    key, new ArrayMap<>());
+            innerMap.put(callingUid, pir);
+            mRestrictedMetricsPirMap.put(key, innerMap);
+        }
+        try {
+            IStatsd statsd = getStatsdNonblocking();
+            if (statsd != null) {
+                return statsd.setRestrictedMetricsChangedOperation(configId, configPackage, pir,
+                        callingUid);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to setRestrictedMetricsChangedOperation with statsd");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return new long[]{};
+    }
+
+    @Override
+    public void removeRestrictedMetricsChangedOperation(long configId, String configPackage) {
+        enforceRestrictedStatsPermission();
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        ConfigKeyWithPackage key = new ConfigKeyWithPackage(configPackage, configId);
+        synchronized (mLock) {
+            ArrayMap<Integer, PendingIntentRef> innerMap = mRestrictedMetricsPirMap.getOrDefault(
+                    key, new ArrayMap<>());
+            innerMap.remove(callingUid);
+            if (innerMap.isEmpty()) {
+                mRestrictedMetricsPirMap.remove(key);
+            }
+        }
+        try {
+            IStatsd statsd = getStatsdNonblocking();
+            if (statsd != null) {
+                statsd.removeRestrictedMetricsChangedOperation(configId, configPackage, callingUid);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to removeRestrictedMetricsChangedOperation with statsd");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void querySql(String sqlQuery, int minSqlClientVersion, byte[] policyConfig,
+            IStatsQueryCallback queryCallback, long configKey, String configPackage) {
+        int callingUid = Binder.getCallingUid();
+        enforceRestrictedStatsPermission();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                statsd.querySql(
+                    sqlQuery,
+                    minSqlClientVersion,
+                    policyConfig,
+                    queryCallback,
+                    configKey,
+                    configPackage,
+                    callingUid);
+            } else {
+                queryCallback.sendFailure("Could not connect to statsd from system server");
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     void setStatsCompanionService(StatsCompanionService statsCompanionService) {
         mStatsCompanionService = statsCompanionService;
     }
 
+    /** Checks that the caller has READ_RESTRICTED_STATS permission. */
+    private void enforceRestrictedStatsPermission() {
+        mContext.enforceCallingPermission(Manifest.permission.READ_RESTRICTED_STATS, null);
+    }
+
     /**
      * Checks that the caller has both DUMP and PACKAGE_USAGE_STATS permissions. Also checks that
      * the caller has USAGE_STATS_PERMISSION_OPS for the specified packageName if it is not null.
@@ -618,6 +747,8 @@
             registerAllDataFetchOperations(statsd);
             registerAllActiveConfigsChangedOperations(statsd);
             registerAllBroadcastSubscribers(statsd);
+            registerAllRestrictedMetricsChangedOperations(statsd);
+            // TODO (b/269419485): register all restricted metric operations.
         } catch (RemoteException e) {
             Log.e(TAG, "StatsManager failed to (re-)register data with statsd");
         } finally {
@@ -686,7 +817,7 @@
         }
 
         for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
-                mBroadcastSubscriberPirMap.entrySet()) {
+                broadcastSubscriberCopy.entrySet()) {
             ConfigKey configKey = entry.getKey();
             for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) {
                 statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
@@ -695,6 +826,29 @@
         }
     }
 
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllRestrictedMetricsChangedOperations(IStatsd statsd)
+            throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a deep copy of
+        // the data with the lock held before iterating through the map.
+        ArrayMap<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>> restrictedMetricsCopy =
+                new ArrayMap<>();
+        synchronized (mLock) {
+            for (Map.Entry<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>> entry :
+                    mRestrictedMetricsPirMap.entrySet()) {
+                restrictedMetricsCopy.put(entry.getKey(), new ArrayMap(entry.getValue()));
+            }
+        }
+
+        for (Map.Entry<ConfigKeyWithPackage, ArrayMap<Integer, PendingIntentRef>> entry :
+                restrictedMetricsCopy.entrySet()) {
+            ConfigKeyWithPackage configKey = entry.getKey();
+            for (Map.Entry<Integer, PendingIntentRef> uidEntry : entry.getValue().entrySet()) {
+                statsd.setRestrictedMetricsChangedOperation(configKey.getConfigId(),
+                        configKey.getConfigPackage(), uidEntry.getValue(), uidEntry.getKey());
+            }
+        }
+    }
     private static final int CHUNK_SIZE = 1024 * 64; // 64 kB
 
     /**
@@ -703,8 +857,8 @@
      * No exception handling in this API since they will not be propagated back to caller
      * to make debugging easier, since this API part of oneway binder call flow.
      */
-    public static void getDataWithFd(IStatsd service, long configKey, int callingUid,
-            ParcelFileDescriptor dstFd)
+    private static void getDataFdFromStatsd(IStatsd service, long configKey, int callingUid,
+            FileDescriptor dstFd)
             throws IllegalStateException, RemoteException {
         ParcelFileDescriptor[] pipe;
         try {
@@ -728,10 +882,13 @@
             throw new IllegalStateException("Failed to close FD.", e);
         }
 
+        // There are many possible exceptions below, to not forget close pipe descriptors
+        // wrapping in the try-with-resources statement
         try (FileInputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readFd);
              DataInputStream srcDataStream = new DataInputStream(inputStream);
-             FileOutputStream outStream = new ParcelFileDescriptor.AutoCloseOutputStream(dstFd);
+             FileOutputStream outStream = new FileOutputStream(dstFd);
              DataOutputStream dstDataStream = new DataOutputStream(outStream)) {
+
             byte[] chunk = new byte[CHUNK_SIZE];
             int chunkLen = 0;
             int readBytes = 0;
diff --git a/statsd/Android.bp b/statsd/Android.bp
index 181a4e3..2199a5c 100644
--- a/statsd/Android.bp
+++ b/statsd/Android.bp
@@ -25,7 +25,34 @@
         "-Wno-deprecated-declarations",
         "-Wthread-safety",
     ],
+    tidy: true,
+    tidy_flags: [
+        // Only check our headers
+        "-header-filter=^packages/modules/StatsD/statsd",
+    ],
 
+    tidy_checks: [
+        "android-*",
+        "bugprone-*",
+        "cert-*",
+        "clang-analyzer-security*",
+        "google-*",
+        "misc-*",
+        "performance-*",
+        "-bugprone-narrowing-conversions", // lots of unsigned -> int conversions
+        "-cert-err34-c",
+        "-cert-msc30-c", // warning: rand() has limited randomness; use C++11 random library
+        "-cert-msc50-cpp", // warning: rand() has limited randomness; use C++11 random library
+    ],
+    tidy_checks_as_errors: [
+        "android-*",
+        "bugprone-*",
+        "cert-*",
+        "clang-analyzer-security*",
+        "google-*",
+        "misc-*",
+        "performance-*",
+    ],
     srcs: [
         "src/active_config_list.proto",
         "src/anomaly/AlarmMonitor.cpp",
@@ -48,6 +75,7 @@
         "src/external/StatsPuller.cpp",
         "src/external/StatsPullerManager.cpp",
         "src/external/TrainInfoPuller.cpp",
+        "src/external/Uprobestats.cpp",
         "src/FieldValue.cpp",
         "src/flags/FlagProvider.cpp",
         "src/guardrail/StatsdStats.cpp",
@@ -55,6 +83,7 @@
         "src/HashableDimensionKey.cpp",
         "src/logd/LogEvent.cpp",
         "src/logd/LogEventQueue.cpp",
+        "src/logd/logevent_util.cpp",
         "src/matchers/CombinationAtomMatchingTracker.cpp",
         "src/matchers/EventMatcherWizard.cpp",
         "src/matchers/matcher_util.cpp",
@@ -65,6 +94,7 @@
         "src/metrics/duration_helper/OringDurationTracker.cpp",
         "src/metrics/DurationMetricProducer.cpp",
         "src/metrics/EventMetricProducer.cpp",
+        "src/metrics/RestrictedEventMetricProducer.cpp",
         "src/metrics/GaugeMetricProducer.cpp",
         "src/metrics/KllMetricProducer.cpp",
         "src/metrics/MetricProducer.cpp",
@@ -81,6 +111,7 @@
         "src/state/StateManager.cpp",
         "src/state/StateTracker.cpp",
         "src/stats_log_util.cpp",
+        "src/stats_policy_config.proto",
         "src/statscompanion_util.cpp",
         "src/statsd_config.proto",
         "src/statsd_metadata.proto",
@@ -92,6 +123,9 @@
         "src/subscriber/SubscriberReporter.cpp",
         "src/uid_data.proto",
         "src/utils/MultiConditionTrigger.cpp",
+        "src/utils/DbUtils.cpp",
+        "src/utils/Regex.cpp",
+        "src/utils/RestrictedPolicyManager.cpp",
         "src/utils/ShardOffsetProvider.cpp",
     ],
 
@@ -110,11 +144,13 @@
         "libutils",
         "server_configurable_flags",
         "statsd-aidl-ndk",
+        "libsqlite_static_noicu",
     ],
     shared_libs: [
         "libbinder_ndk",
         "libincident",
         "liblog",
+        "libstatssocket",
     ],
     header_libs: [
         "libgtest_prod_headers",
@@ -124,7 +160,10 @@
 genrule {
     name: "statslog_statsd.h",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsd.h --module statsd --namespace android,os,statsd,util",
+    cmd: "$(location stats-log-api-gen) " +
+        "--header $(genDir)/statslog_statsd.h " +
+        "--module statsd " +
+        "--namespace android,os,statsd,util",
     out: [
         "statslog_statsd.h",
     ],
@@ -133,7 +172,11 @@
 genrule {
     name: "statslog_statsd.cpp",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsd.cpp --module statsd --namespace android,os,statsd,util --importHeader statslog_statsd.h",
+    cmd: "$(location stats-log-api-gen) " +
+        "--cpp $(genDir)/statslog_statsd.cpp " +
+        "--module statsd " +
+        "--namespace android,os,statsd,util " +
+        "--importHeader statslog_statsd.h",
     out: [
         "statslog_statsd.cpp",
     ],
@@ -142,7 +185,10 @@
 genrule {
     name: "statslog_statsdtest.h",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_statsdtest.h --module statsdtest --namespace android,os,statsd,util",
+    cmd: "$(location stats-log-api-gen) " +
+        "--header $(genDir)/statslog_statsdtest.h " +
+        "--module statsdtest " +
+        "--namespace android,os,statsd,util",
     out: [
         "statslog_statsdtest.h",
     ],
@@ -151,7 +197,11 @@
 genrule {
     name: "statslog_statsdtest.cpp",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_statsdtest.cpp --module statsdtest --namespace android,os,statsd,util --importHeader statslog_statsdtest.h",
+    cmd: "$(location stats-log-api-gen) " +
+        "--cpp $(genDir)/statslog_statsdtest.cpp " +
+        "--module statsdtest " +
+        "--namespace android,os,statsd,util " +
+        "--importHeader statslog_statsdtest.h",
     out: [
         "statslog_statsdtest.cpp",
     ],
@@ -208,25 +258,12 @@
         // "-O0",
     ],
 
-    product_variables: {
-        eng: {
-            // Enable sanitizer ONLY on eng builds
-            //sanitize: {
-            //    address: true,
-            //},
-        },
-    },
-
     proto: {
         type: "lite",
         static: true,
     },
     stl: "libc++_static",
 
-    shared_libs: [
-        "libstatssocket",
-    ],
-
     apex_available: [
         "com.android.os.statsd",
         "test_com.android.os.statsd",
@@ -234,13 +271,59 @@
     min_sdk_version: "30",
 }
 
+cc_defaults {
+    name: "statsd_test_defaults",
+    defaults: ["statsd_defaults"],
+    srcs: [
+        // atom_field_options.proto needs field_options.proto, but that is
+        // not included in libprotobuf-cpp-lite, so compile it here.
+        ":libprotobuf-internal-protos",
+        ":libstats_internal_protos",
+
+        "src/shell/shell_data.proto",
+        "src/stats_log.proto",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-enum-compare",
+        "-Wno-missing-field-initializers",
+        "-Wno-unused-function",
+        "-Wno-unused-parameter",
+        "-Wno-unused-variable",
+    ],
+    static_libs: [
+        "libgmock",
+        "libstatslog_statsdtest",
+    ],
+    proto: {
+        type: "lite",
+        include_dirs: [
+            "external/protobuf/src",
+            "frameworks/proto_logging/stats",
+        ],
+        static: true,
+    },
+}
+
+cc_library_static {
+    name: "libstats_test_utils",
+    defaults: ["statsd_test_defaults"],
+    srcs: [
+        "tests/statsd_test_util.cpp",
+    ],
+    tidy_timeout_srcs: [
+        "tests/statsd_test_util.cpp",
+    ],
+}
+
 // ==============
 // statsd_test
 // ==============
 
 cc_test {
     name: "statsd_test",
-    defaults: ["statsd_defaults"],
+    defaults: ["statsd_test_defaults"],
     test_suites: [
         "device-tests",
         "mts-statsd",
@@ -259,16 +342,6 @@
         },
     },
 
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-enum-compare",
-        "-Wno-missing-field-initializers",
-        "-Wno-unused-function",
-        "-Wno-unused-parameter",
-        "-Wno-unused-variable",
-    ],
-
     require_root: true,
 
     tidy_timeout_srcs: [
@@ -298,23 +371,16 @@
         "tests/metrics/MaxDurationTracker_test.cpp",
         "tests/metrics/NumericValueMetricProducer_test.cpp",
         "tests/metrics/OringDurationTracker_test.cpp",
+        "tests/metrics/RestrictedEventMetricProducer_test.cpp",
         "tests/MetricsManager_test.cpp",
         "tests/metrics/parsing_utils/config_update_utils_test.cpp",
         "tests/metrics/parsing_utils/metrics_manager_util_test.cpp",
         "tests/state/StateTracker_test.cpp",
-        "tests/statsd_test_util.cpp",
         "tests/StatsLogProcessor_test.cpp",
         "tests/UidMap_test.cpp",
     ],
 
     srcs: [
-        // atom_field_options.proto needs field_options.proto, but that is
-        // not included in libprotobuf-cpp-lite, so compile it here.
-        ":libprotobuf-internal-protos",
-        ":libstats_internal_protos",
-
-        "src/shell/shell_data.proto",
-        "src/stats_log.proto",
         "tests/AlarmMonitor_test.cpp",
         "tests/anomaly/AlarmTracker_test.cpp",
         "tests/anomaly/AnomalyTracker_test.cpp",
@@ -338,6 +404,9 @@
         "tests/e2e/MetricActivation_e2e_test.cpp",
         "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/external/puller_util_test.cpp",
@@ -362,6 +431,7 @@
         "tests/metrics/metrics_test_helper.cpp",
         "tests/metrics/OringDurationTracker_test.cpp",
         "tests/metrics/NumericValueMetricProducer_test.cpp",
+        "tests/metrics/RestrictedEventMetricProducer_test.cpp",
         "tests/metrics/parsing_utils/config_update_utils_test.cpp",
         "tests/metrics/parsing_utils/metrics_manager_util_test.cpp",
         "tests/subscriber/SubscriberReporter_test.cpp",
@@ -369,7 +439,6 @@
         "tests/MetricsManager_test.cpp",
         "tests/shell/ShellSubscriber_test.cpp",
         "tests/state/StateTracker_test.cpp",
-        "tests/statsd_test_util.cpp",
         "tests/statsd_test_util_test.cpp",
         "tests/SocketListener_test.cpp",
         "tests/StatsLogProcessor_test.cpp",
@@ -377,25 +446,16 @@
         "tests/storage/StorageManager_test.cpp",
         "tests/UidMap_test.cpp",
         "tests/utils/MultiConditionTrigger_test.cpp",
+        "tests/utils/DbUtils_test.cpp",
     ],
 
     static_libs: [
-        "libgmock",
         "libstatsgtestmatchers",
-        "libstatslog_statsdtest",
-        "libstatssocket_private",
+        "libstats_test_utils",
     ],
 
-    proto: {
-        type: "lite",
-        include_dirs: [
-            "external/protobuf/src",
-            "frameworks/proto_logging/stats",
-        ],
-        static: true,
-    },
     min_sdk_version: "30",
-
+    test_for: ["com.android.os.statsd"],
 }
 
 //#############################
@@ -404,14 +464,11 @@
 
 cc_benchmark {
     name: "statsd_benchmark",
-    defaults: ["statsd_defaults"],
+    defaults: ["statsd_test_defaults"],
 
     srcs: [
-        // atom_field_options.proto needs field_options.proto, but that is
-        // not included in libprotobuf-cpp-lite, so compile it here.
-        ":libprotobuf-internal-protos",
-        ":libstats_internal_protos",
-
+        "benchmark/data_structures_benchmark.cpp",
+        "benchmark/db_benchmark.cpp",
         "benchmark/duration_metric_benchmark.cpp",
         "benchmark/filter_value_benchmark.cpp",
         "benchmark/get_dimensions_for_condition_benchmark.cpp",
@@ -419,43 +476,23 @@
         "benchmark/log_event_benchmark.cpp",
         "benchmark/log_event_filter_benchmark.cpp",
         "benchmark/main.cpp",
-        "benchmark/metric_util.cpp",
+        "benchmark/on_log_event_benchmark.cpp",
         "benchmark/stats_write_benchmark.cpp",
-        "src/stats_log.proto",
+        "benchmark/loss_info_container_benchmark.cpp",
+        "benchmark/string_transform_benchmark.cpp",
     ],
 
-    proto: {
-        type: "lite",
-        include_dirs: [
-            "external/protobuf/src",
-            "frameworks/proto_logging/stats",
-        ],
-    },
-
     cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-        "-Wno-unused-variable",
-        "-Wno-unused-function",
-
         // Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
         "-Wno-varargs",
     ],
 
     static_libs: [
-        "libplatformprotos",
-        "libstatssocket_private",
+        "libgtest",
+        "libstats_test_utils",
     ],
 
-    shared_libs: [
-        "libprotobuf-cpp-lite",
-        "libstatslog",
-    ],
-
-    header_libs: [
-        "libgtest_prod_headers",
-    ],
+    test_for: ["com.android.os.statsd"],
 }
 
 // ====  java proto device library (for test only)  ==============================
@@ -547,6 +584,68 @@
     ],
 }
 
+cc_fuzz {
+    name: "statsd_service_fuzzer",
+    defaults: [
+        "statsd_defaults",
+        "service_fuzzer_defaults",
+        "fuzzer_disable_leaks",
+    ],
+    srcs: [
+        "fuzzers/statsd_service_fuzzer.cpp",
+    ],
+    shared_libs: [
+        "libstatssocket",
+        "libvndksupport",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    fuzz_config: {
+        triage_assignee: "waghpawan@google.com",
+        cc: [
+            "singhtejinder@google.com",
+            "sharaienko@google.com",
+        ],
+    },
+    proto: {
+        type: "lite",
+        static: true,
+    },
+}
+
+cc_fuzz {
+    name: "statsd_fuzzer",
+    defaults: [
+        "statsd_defaults",
+    ],
+    srcs: [
+        "fuzzers/statsd_socket_data_fuzzer.cpp",
+    ],
+    shared_libs: [
+        "libstatssocket",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    fuzz_config: {
+        cc: [
+            "singhtejinder@google.com",
+            "sharaienko@google.com",
+        ],
+    },
+    proto: {
+        type: "lite",
+        static: true,
+    },
+}
+
 // Filegroup for subscription protos.
 filegroup {
     name: "libstats_subscription_protos",
@@ -557,4 +656,3 @@
         "src/shell/shell_data.proto",
     ],
 }
-
diff --git a/statsd/benchmark/data_structures_benchmark.cpp b/statsd/benchmark/data_structures_benchmark.cpp
new file mode 100644
index 0000000..5ee451c
--- /dev/null
+++ b/statsd/benchmark/data_structures_benchmark.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cstdlib>
+#include <ctime>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+template <typename ContainerType>
+void benchmarkFunctionForVector(std::vector<ContainerType>& vec, int capacity) {
+    ContainerType result = false;
+    for (int i = 0; i < capacity; i++) {
+        vec[i] = !result;
+        result = !result;
+    }
+
+    int resultInt = 0;
+    for (int i = 0; i < capacity; i++) {
+        resultInt += vec[i];
+    }
+
+    // Make sure the variable is not optimized away by compiler
+    benchmark::DoNotOptimize(vec);
+    benchmark::DoNotOptimize(resultInt);
+}
+
+template <typename ContainerType>
+void benchmarkStdFillForVector(std::vector<ContainerType>& vec, int capacity) {
+    std::fill(vec.begin(), vec.end(), true);
+    int resultInt = 0;
+    for (int i = 0; i < capacity; i++) {
+        resultInt += vec[i];
+    }
+
+    // Make sure the variable is not optimized away by compiler
+    benchmark::DoNotOptimize(vec);
+    benchmark::DoNotOptimize(resultInt);
+}
+
+}  //  namespace
+
+static void BM_BasicVectorBoolUsage(benchmark::State& state) {
+    const int capacity = state.range(0);
+    std::vector<bool> vec(capacity);
+
+    while (state.KeepRunning()) {
+        benchmarkFunctionForVector<bool>(vec, capacity);
+    }
+}
+BENCHMARK(BM_BasicVectorBoolUsage)->Args({5})->Args({10})->Args({20})->Args({50})->Args({100});
+
+static void BM_VectorBoolStdFill(benchmark::State& state) {
+    const int capacity = state.range(0);
+    std::vector<bool> vec(capacity);
+
+    while (state.KeepRunning()) {
+        benchmarkStdFillForVector<bool>(vec, capacity);
+    }
+}
+BENCHMARK(BM_VectorBoolStdFill)->Args({5})->Args({10})->Args({20})->Args({50})->Args({100});
+
+static void BM_BasicVectorInt8Usage(benchmark::State& state) {
+    const int capacity = state.range(0);
+    std::vector<int8_t> vec(capacity);
+
+    while (state.KeepRunning()) {
+        benchmarkFunctionForVector<int8_t>(vec, capacity);
+    }
+}
+BENCHMARK(BM_BasicVectorInt8Usage)->Args({5})->Args({10})->Args({20})->Args({50})->Args({100});
+
+static void BM_VectorInt8StdFill(benchmark::State& state) {
+    const int capacity = state.range(0);
+    std::vector<int8_t> vec(capacity);
+
+    while (state.KeepRunning()) {
+        benchmarkStdFillForVector<int8_t>(vec, capacity);
+    }
+}
+BENCHMARK(BM_VectorInt8StdFill)->Args({5})->Args({10})->Args({20})->Args({50})->Args({100});
+
+}  //  namespace statsd
+}  //  namespace os
+}  //  namespace android
diff --git a/statsd/benchmark/db_benchmark.cpp b/statsd/benchmark/db_benchmark.cpp
new file mode 100644
index 0000000..b9b1694
--- /dev/null
+++ b/statsd/benchmark/db_benchmark.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "benchmark/benchmark.h"
+#include "tests/statsd_test_util.h"
+#include "utils/DbUtils.h"
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+namespace dbutils {
+
+static void BM_insertAtomsIntoDbTablesNewConnection(benchmark::State& state) {
+    ConfigKey key = ConfigKey(111, 222);
+    int64_t metricId = 0;
+    int64_t bucketStartTimeNs = 10000000000;
+
+    unique_ptr<LogEvent> event =
+            CreateScreenStateChangedEvent(bucketStartTimeNs, android::view::DISPLAY_STATE_OFF);
+    vector<LogEvent> logEvents;
+    for (int j = 0; j < state.range(1); ++j) {
+        logEvents.push_back(*event.get());
+    }
+    string err;
+    for (auto s : state) {
+        for (int metricId = 0; metricId < state.range(0); ++metricId) {
+            state.PauseTiming();
+            deleteDb(key);
+            createTableIfNeeded(key, metricId, *event.get());
+            state.ResumeTiming();
+            insert(key, metricId, logEvents, err);
+        }
+    }
+    deleteDb(key);
+}
+
+BENCHMARK(BM_insertAtomsIntoDbTablesNewConnection)
+        ->Args({1, 10})
+        ->Args({1, 50})
+        ->Args({1, 100})
+        ->Args({1, 500})
+        ->Args({10, 10})
+        ->Args({10, 20});
+
+static void BM_insertAtomsIntoDbTablesReuseConnection(benchmark::State& state) {
+    ConfigKey key = ConfigKey(111, 222);
+    int64_t metricId = 0;
+    int64_t bucketStartTimeNs = 10000000000;
+
+    unique_ptr<LogEvent> event =
+            CreateScreenStateChangedEvent(bucketStartTimeNs, android::view::DISPLAY_STATE_OFF);
+    vector<LogEvent> logEvents;
+    for (int j = 0; j < state.range(1); ++j) {
+        logEvents.push_back(*event.get());
+    }
+    sqlite3* dbHandle = getDb(key);
+    string err;
+    for (auto s : state) {
+        for (int metricId = 0; metricId < state.range(0); ++metricId) {
+            state.PauseTiming();
+            deleteTable(key, metricId);
+            createTableIfNeeded(key, metricId, *event.get());
+            state.ResumeTiming();
+            insert(key, metricId, logEvents, err);
+        }
+    }
+    closeDb(dbHandle);
+    deleteDb(key);
+}
+
+BENCHMARK(BM_insertAtomsIntoDbTablesReuseConnection)
+        ->Args({1, 10})
+        ->Args({1, 50})
+        ->Args({1, 100})
+        ->Args({1, 500})
+        ->Args({10, 10})
+        ->Args({10, 20});
+
+static void BM_createDbTables(benchmark::State& state) {
+    ConfigKey key = ConfigKey(111, 222);
+    int64_t metricId = 0;
+    int64_t bucketStartTimeNs = 10000000000;
+
+    unique_ptr<LogEvent> event =
+            CreateScreenStateChangedEvent(bucketStartTimeNs, android::view::DISPLAY_STATE_OFF);
+    vector<LogEvent> logEvents{*event.get()};
+    string err;
+    for (auto s : state) {
+        state.PauseTiming();
+        deleteTable(key, metricId);
+        state.ResumeTiming();
+        createTableIfNeeded(key, metricId, *event.get());
+        insert(key, metricId, logEvents, err);
+    }
+    deleteDb(key);
+}
+
+BENCHMARK(BM_createDbTables);
+}  // namespace dbutils
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/benchmark/duration_metric_benchmark.cpp b/statsd/benchmark/duration_metric_benchmark.cpp
index 2d315d9..a87b5ee 100644
--- a/statsd/benchmark/duration_metric_benchmark.cpp
+++ b/statsd/benchmark/duration_metric_benchmark.cpp
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 #include <vector>
-#include "benchmark/benchmark.h"
+
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
+#include "benchmark/benchmark.h"
 #include "logd/LogEvent.h"
 #include "stats_log_util.h"
-#include "metric_util.h"
+#include "tests/statsd_test_util.h"
 
 namespace android {
 namespace os {
@@ -39,15 +40,15 @@
 
     auto scheduledJobPredicate = CreateScheduledJobPredicate();
     auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
-    dimensions->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
+    dimensions->set_field(util::SCHEDULED_JOB_STATE_CHANGED);
     dimensions->add_child()->set_field(2);  // job name field.
 
     auto screenIsOffPredicate = CreateScreenIsOffPredicate();
 
     auto isSyncingPredicate = CreateIsSyncingPredicate();
     auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidAndTagDimensions(android::util::SYNC_STATE_CHANGED,
-                                                          {Position::FIRST});
+    *syncDimension =
+            CreateAttributionUidAndTagDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
     if (addExtraDimensionInCondition) {
         syncDimension->add_child()->set_field(2 /* name field*/);
     }
@@ -68,10 +69,10 @@
     metric->set_condition(combinationPredicate->id());
     metric->set_aggregation_type(aggregationType);
     auto dimensionWhat = metric->mutable_dimensions_in_what();
-    dimensionWhat->set_field(android::util::SCHEDULED_JOB_STATE_CHANGED);
+    dimensionWhat->set_field(util::SCHEDULED_JOB_STATE_CHANGED);
     dimensionWhat->add_child()->set_field(2);  // job name field.
-    *metric->mutable_dimensions_in_condition() = CreateAttributionUidAndTagDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+    *metric->mutable_dimensions_in_condition() =
+            CreateAttributionUidAndTagDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
     return config;
 }
 
@@ -87,14 +88,13 @@
 
     auto scheduledJobPredicate = CreateScheduledJobPredicate();
     auto dimensions = scheduledJobPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *dimensions = CreateAttributionUidDimensions(
-                android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+    *dimensions =
+            CreateAttributionUidDimensions(util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
     dimensions->add_child()->set_field(2);  // job name field.
 
     auto isSyncingPredicate = CreateIsSyncingPredicate();
     auto syncDimension = isSyncingPredicate.mutable_simple_predicate()->mutable_dimensions();
-    *syncDimension = CreateAttributionUidDimensions(
-            android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+    *syncDimension = CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
     if (addExtraDimensionInCondition) {
         syncDimension->add_child()->set_field(2 /* name field*/);
     }
@@ -116,16 +116,15 @@
     metric->set_what(scheduledJobPredicate.id());
     metric->set_condition(combinationPredicate->id());
     metric->set_aggregation_type(aggregationType);
-    *metric->mutable_dimensions_in_what() = CreateAttributionUidDimensions(
-            android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+    *metric->mutable_dimensions_in_what() =
+            CreateAttributionUidDimensions(util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
 
     auto links = metric->add_links();
     links->set_condition(isSyncingPredicate.id());
     *links->mutable_fields_in_what() =
-            CreateAttributionUidDimensions(
-                android::util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
+            CreateAttributionUidDimensions(util::SCHEDULED_JOB_STATE_CHANGED, {Position::FIRST});
     *links->mutable_fields_in_condition() =
-            CreateAttributionUidDimensions(android::util::SYNC_STATE_CHANGED, {Position::FIRST});
+            CreateAttributionUidDimensions(util::SYNC_STATE_CHANGED, {Position::FIRST});
     return config;
 }
 
@@ -208,8 +207,8 @@
     sortLogEventsByTimestamp(&events);
 
     while (state.KeepRunning()) {
-        auto processor = CreateStatsLogProcessor(
-                bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+        auto processor =
+                CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
         for (const auto& event : events) {
             processor->OnLogEvent(event.get());
         }
@@ -299,8 +298,8 @@
     sortLogEventsByTimestamp(&events);
 
     while (state.KeepRunning()) {
-        auto processor = CreateStatsLogProcessor(
-                bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+        auto processor =
+                CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
         for (const auto& event : events) {
             processor->OnLogEvent(event.get());
         }
diff --git a/statsd/benchmark/filter_value_benchmark.cpp b/statsd/benchmark/filter_value_benchmark.cpp
index 743ccc4..0642070 100644
--- a/statsd/benchmark/filter_value_benchmark.cpp
+++ b/statsd/benchmark/filter_value_benchmark.cpp
@@ -19,9 +19,9 @@
 #include "HashableDimensionKey.h"
 #include "benchmark/benchmark.h"
 #include "logd/LogEvent.h"
-#include "metric_util.h"
 #include "stats_event.h"
 #include "stats_log_util.h"
+#include "tests/statsd_test_util.h"
 
 namespace android {
 namespace os {
diff --git a/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp b/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
index 7a45565..9957066 100644
--- a/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
+++ b/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
@@ -19,9 +19,9 @@
 #include "HashableDimensionKey.h"
 #include "benchmark/benchmark.h"
 #include "logd/LogEvent.h"
-#include "metric_util.h"
 #include "stats_event.h"
 #include "stats_log_util.h"
+#include "tests/statsd_test_util.h"
 
 namespace android {
 namespace os {
diff --git a/statsd/benchmark/loss_info_container_benchmark.cpp b/statsd/benchmark/loss_info_container_benchmark.cpp
new file mode 100644
index 0000000..634a173
--- /dev/null
+++ b/statsd/benchmark/loss_info_container_benchmark.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <cstdlib>
+#include <ctime>
+#include <map>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+std::vector<int> generateRandomIds(int count, int maxRange) {
+    std::srand(std::time(nullptr));
+
+    std::unordered_set<int> unique_values;
+
+    while (unique_values.size() <= count) {
+        unique_values.insert(std::rand() % maxRange);
+    }
+
+    std::vector<int> result(unique_values.begin(), unique_values.end());
+
+    return result;
+}
+
+const int kMaxAtomId = 100000;
+const int kMaxErrorCode = 20;
+
+std::vector<std::pair<std::vector<int>, std::vector<int>>> generateIdsAndErrorsVectors(
+        const std::vector<int>& idsCounts, const std::vector<int>& errorsCounts) {
+    std::vector<std::pair<std::vector<int>, std::vector<int>>> result;
+    for (const int idCount : idsCounts) {
+        for (const int errorCount : errorsCounts) {
+            auto ids = generateRandomIds(idCount, kMaxAtomId);
+            auto errors = generateRandomIds(errorCount, kMaxErrorCode);
+            result.push_back(std::make_pair(ids, errors));
+        }
+    }
+    return result;
+}
+
+const std::vector<std::pair<std::vector<int>, std::vector<int>>> kRandomIdsAndErrorsPairs =
+        generateIdsAndErrorsVectors({1, 5, 10, 50}, {1, 2, 5, 10});
+
+struct TestVector {
+    std::vector<int> errors;
+    std::vector<int> tags;
+};
+
+std::vector<TestVector> generateTestVector(
+        int count,
+        const std::vector<std::pair<std::vector<int>, std::vector<int>>>& idsAndErrorsPairs) {
+    std::srand(std::time(nullptr));
+
+    std::vector<TestVector> result;
+
+    for (const auto& idsAndErrors : idsAndErrorsPairs) {
+        TestVector testVector;
+
+        testVector.errors.reserve(count);
+        testVector.tags.reserve(count);
+
+        for (int i = 0; i < count; i++) {
+            const int randomAtomIdFromReferenceList =
+                    idsAndErrors.first[std::rand() % idsAndErrors.first.size()];
+            const int randomErrorFromReferenceList =
+                    idsAndErrors.second[std::rand() % idsAndErrors.second.size()];
+
+            testVector.errors.push_back(randomErrorFromReferenceList);
+            testVector.tags.push_back(randomAtomIdFromReferenceList);
+        }
+        result.push_back(testVector);
+    }
+
+    return result;
+}
+
+constexpr int kTestVectorSize = 4096;
+constexpr int kMaxAtomTagsCount = 100;
+
+const std::vector<TestVector> kTestVectors =
+        generateTestVector(kTestVectorSize, kRandomIdsAndErrorsPairs);
+
+struct LossInfoVector {
+    // using vectors is more memory efficient
+    // using vectors fits well with the dump API implementation - no need to transform data
+    // before writing into AStatsEvent since it is aligned with repeated int32 fields
+    std::vector<int> errors;
+    std::vector<int> tags;
+    std::vector<int> counts;
+
+    bool noteLossInfo(int error, int tag) {
+        // linear search is Ok here since we do not expect to see many tags, usually 1-5 per uid
+        // exception is system server where we see 10s atoms
+        size_t locatedTagIndex = 0;
+        for (locatedTagIndex = 0; locatedTagIndex < errors.size(); ++locatedTagIndex) {
+            // is there already logged an atom with tag == atomId
+            if (errors[locatedTagIndex] == error && tags[locatedTagIndex] == tag) {
+                ++counts[locatedTagIndex];
+                return true;
+            }
+        }
+
+        // if pair [error, atomId] is not found and guardrail is not reached yet store loss
+        // counter
+        if (locatedTagIndex == errors.size() && tags.size() < kMaxAtomTagsCount) {
+            errors.push_back(error);
+            tags.push_back(tag);
+            counts.push_back(1);
+        } else {
+            return false;
+        }
+        return true;
+    }
+};
+
+using LossInfoKey = std::pair<int, int>;  // [error, tag]
+
+template <typename T>
+struct LossInfoMap {
+    // using maps is more CPU efficient however will require some postprocessing before
+    // writing into the socket
+    T countsPerErrorTag;
+
+    bool noteLossInfo(int error, int tag) {
+        LossInfoKey key = std::make_pair(error, tag);
+        auto counterIt = countsPerErrorTag.find(key);
+
+        if (counterIt != countsPerErrorTag.end()) {
+            ++counterIt->second;
+        } else if (countsPerErrorTag.size() < kMaxAtomTagsCount) {
+            countsPerErrorTag[key] = 1;
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+};
+
+}  // namespace
+
+struct hash_pair final {
+    template <class TFirst, class TSecond>
+    size_t operator()(const std::pair<TFirst, TSecond>& p) const noexcept {
+        uintmax_t hash = std::hash<TFirst>{}(p.first);
+        hash <<= sizeof(uintmax_t) * 4;
+        hash ^= std::hash<TSecond>{}(p.second);
+        return std::hash<uintmax_t>{}(hash);
+    }
+};
+
+static void BM_LossInfoCollectionAndDumpViaVector(benchmark::State& state) {
+    const TestVector& testVector = kTestVectors[state.range(0)];
+    LossInfoVector lossInfo;
+
+    while (state.KeepRunning()) {
+        int res = 0;
+        for (int i = 0; i < kTestVectorSize; i++) {
+            res += lossInfo.noteLossInfo(testVector.errors[i], testVector.tags[i]);
+        }
+        benchmark::DoNotOptimize(res);
+    }
+}
+BENCHMARK(BM_LossInfoCollectionAndDumpViaVector)
+        ->Args({0})
+        ->Args({1})
+        ->Args({2})
+        ->Args({3})
+        ->Args({4})
+        ->Args({5})
+        ->Args({6})
+        ->Args({7})
+        ->Args({8})
+        ->Args({9})
+        ->Args({10})
+        ->Args({11})
+        ->Args({12})
+        ->Args({13})
+        ->Args({14})
+        ->Args({15});
+
+static void BM_LossInfoCollectionAndDumpViaUnorderedMap(benchmark::State& state) {
+    const TestVector& testVector = kTestVectors[state.range(0)];
+    LossInfoMap<std::unordered_map<LossInfoKey, int, hash_pair>> lossInfo;
+
+    while (state.KeepRunning()) {
+        int res = 0;
+        for (int i = 0; i < kTestVectorSize; i++) {
+            res += lossInfo.noteLossInfo(testVector.errors[i], testVector.tags[i]);
+        }
+        benchmark::DoNotOptimize(res);
+    }
+}
+BENCHMARK(BM_LossInfoCollectionAndDumpViaUnorderedMap)
+        ->Args({0})
+        ->Args({1})
+        ->Args({2})
+        ->Args({3})
+        ->Args({4})
+        ->Args({5})
+        ->Args({6})
+        ->Args({7})
+        ->Args({8})
+        ->Args({9})
+        ->Args({10})
+        ->Args({11})
+        ->Args({12})
+        ->Args({13})
+        ->Args({14})
+        ->Args({15});
+
+}  //  namespace statsd
+}  //  namespace os
+}  //  namespace android
diff --git a/statsd/benchmark/metric_util.cpp b/statsd/benchmark/metric_util.cpp
deleted file mode 100644
index 66c4364..0000000
--- a/statsd/benchmark/metric_util.cpp
+++ /dev/null
@@ -1,380 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "metric_util.h"
-
-#include "stats_event.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) {
-    AtomMatcher atom_matcher;
-    atom_matcher.set_id(StringToId(name));
-    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(atomId);
-    return atom_matcher;
-}
-
-AtomMatcher CreateScheduledJobStateChangedAtomMatcher(const string& name,
-                                                      ScheduledJobStateChanged::State state) {
-    AtomMatcher atom_matcher;
-    atom_matcher.set_id(StringToId(name));
-    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::SCHEDULED_JOB_STATE_CHANGED);
-    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
-    field_value_matcher->set_field(3);  // State field.
-    field_value_matcher->set_eq_int(state);
-    return atom_matcher;
-}
-
-AtomMatcher CreateStartScheduledJobAtomMatcher() {
-    return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobStart",
-                                                     ScheduledJobStateChanged::STARTED);
-}
-
-AtomMatcher CreateFinishScheduledJobAtomMatcher() {
-    return CreateScheduledJobStateChangedAtomMatcher("ScheduledJobFinish",
-                                                     ScheduledJobStateChanged::FINISHED);
-}
-
-AtomMatcher CreateScreenBrightnessChangedAtomMatcher() {
-    AtomMatcher atom_matcher;
-    atom_matcher.set_id(StringToId("ScreenBrightnessChanged"));
-    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::SCREEN_BRIGHTNESS_CHANGED);
-    return atom_matcher;
-}
-
-AtomMatcher CreateUidProcessStateChangedAtomMatcher() {
-    AtomMatcher atom_matcher;
-    atom_matcher.set_id(StringToId("UidProcessStateChanged"));
-    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::UID_PROCESS_STATE_CHANGED);
-    return atom_matcher;
-}
-
-AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name,
-                                                  WakelockStateChanged::State state) {
-    AtomMatcher atom_matcher;
-    atom_matcher.set_id(StringToId(name));
-    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::WAKELOCK_STATE_CHANGED);
-    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
-    field_value_matcher->set_field(4);  // State field.
-    field_value_matcher->set_eq_int(state);
-    return atom_matcher;
-}
-
-AtomMatcher CreateAcquireWakelockAtomMatcher() {
-    return CreateWakelockStateChangedAtomMatcher("AcquireWakelock", WakelockStateChanged::ACQUIRE);
-}
-
-AtomMatcher CreateReleaseWakelockAtomMatcher() {
-    return CreateWakelockStateChangedAtomMatcher("ReleaseWakelock", WakelockStateChanged::RELEASE);
-}
-
-AtomMatcher CreateScreenStateChangedAtomMatcher(
-    const string& name, android::view::DisplayStateEnum state) {
-    AtomMatcher atom_matcher;
-    atom_matcher.set_id(StringToId(name));
-    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::SCREEN_STATE_CHANGED);
-    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
-    field_value_matcher->set_field(1);  // State field.
-    field_value_matcher->set_eq_int(state);
-    return atom_matcher;
-}
-
-AtomMatcher CreateScreenTurnedOnAtomMatcher() {
-    return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn",
-            android::view::DisplayStateEnum::DISPLAY_STATE_ON);
-}
-
-AtomMatcher CreateScreenTurnedOffAtomMatcher() {
-    return CreateScreenStateChangedAtomMatcher("ScreenTurnedOff",
-            ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
-}
-
-AtomMatcher CreateSyncStateChangedAtomMatcher(
-    const string& name, SyncStateChanged::State state) {
-    AtomMatcher atom_matcher;
-    atom_matcher.set_id(StringToId(name));
-    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::SYNC_STATE_CHANGED);
-    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
-    field_value_matcher->set_field(3);  // State field.
-    field_value_matcher->set_eq_int(state);
-    return atom_matcher;
-}
-
-AtomMatcher CreateSyncStartAtomMatcher() {
-    return CreateSyncStateChangedAtomMatcher("SyncStart", SyncStateChanged::ON);
-}
-
-AtomMatcher CreateSyncEndAtomMatcher() {
-    return CreateSyncStateChangedAtomMatcher("SyncEnd", SyncStateChanged::OFF);
-}
-
-AtomMatcher CreateActivityForegroundStateChangedAtomMatcher(
-    const string& name, ActivityForegroundStateChanged::State state) {
-    AtomMatcher atom_matcher;
-    atom_matcher.set_id(StringToId(name));
-    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
-    simple_atom_matcher->set_atom_id(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
-    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
-    field_value_matcher->set_field(4);  // Activity field.
-    field_value_matcher->set_eq_int(state);
-    return atom_matcher;
-}
-
-AtomMatcher CreateMoveToBackgroundAtomMatcher() {
-    return CreateActivityForegroundStateChangedAtomMatcher(
-        "MoveToBackground", ActivityForegroundStateChanged::BACKGROUND);
-}
-
-AtomMatcher CreateMoveToForegroundAtomMatcher() {
-    return CreateActivityForegroundStateChangedAtomMatcher(
-        "MoveToForeground", ActivityForegroundStateChanged::FOREGROUND);
-}
-
-Predicate CreateScheduledJobPredicate() {
-    Predicate predicate;
-    predicate.set_id(StringToId("ScheduledJobRunningPredicate"));
-    predicate.mutable_simple_predicate()->set_start(StringToId("ScheduledJobStart"));
-    predicate.mutable_simple_predicate()->set_stop(StringToId("ScheduledJobFinish"));
-    return predicate;
-}
-
-Predicate CreateBatterySaverModePredicate() {
-    Predicate predicate;
-    predicate.set_id(StringToId("BatterySaverIsOn"));
-    predicate.mutable_simple_predicate()->set_start(StringToId("BatterySaverModeStart"));
-    predicate.mutable_simple_predicate()->set_stop(StringToId("BatterySaverModeStop"));
-    return predicate;
-}
-
-Predicate CreateScreenIsOnPredicate() {
-    Predicate predicate;
-    predicate.set_id(StringToId("ScreenIsOn"));
-    predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOn"));
-    predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOff"));
-    return predicate;
-}
-
-Predicate CreateScreenIsOffPredicate() {
-    Predicate predicate;
-    predicate.set_id(1111123);
-    predicate.mutable_simple_predicate()->set_start(StringToId("ScreenTurnedOff"));
-    predicate.mutable_simple_predicate()->set_stop(StringToId("ScreenTurnedOn"));
-    return predicate;
-}
-
-Predicate CreateHoldingWakelockPredicate() {
-    Predicate predicate;
-    predicate.set_id(StringToId("HoldingWakelock"));
-    predicate.mutable_simple_predicate()->set_start(StringToId("AcquireWakelock"));
-    predicate.mutable_simple_predicate()->set_stop(StringToId("ReleaseWakelock"));
-    return predicate;
-}
-
-Predicate CreateIsSyncingPredicate() {
-    Predicate predicate;
-    predicate.set_id(33333333333333);
-    predicate.mutable_simple_predicate()->set_start(StringToId("SyncStart"));
-    predicate.mutable_simple_predicate()->set_stop(StringToId("SyncEnd"));
-    return predicate;
-}
-
-Predicate CreateIsInBackgroundPredicate() {
-    Predicate predicate;
-    predicate.set_id(StringToId("IsInBackground"));
-    predicate.mutable_simple_predicate()->set_start(StringToId("MoveToBackground"));
-    predicate.mutable_simple_predicate()->set_stop(StringToId("MoveToForeground"));
-    return predicate;
-}
-
-void addPredicateToPredicateCombination(const Predicate& predicate,
-                                        Predicate* combinationPredicate) {
-    combinationPredicate->mutable_combination()->add_predicate(predicate.id());
-}
-
-FieldMatcher CreateAttributionUidDimensions(const int atomId,
-                                            const std::vector<Position>& positions) {
-    FieldMatcher dimensions;
-    dimensions.set_field(atomId);
-    for (const auto position : positions) {
-        auto child = dimensions.add_child();
-        child->set_field(1);
-        child->set_position(position);
-        child->add_child()->set_field(1);
-    }
-    return dimensions;
-}
-
-FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
-                                                 const std::vector<Position>& positions) {
-    FieldMatcher dimensions;
-    dimensions.set_field(atomId);
-    for (const auto position : positions) {
-        auto child = dimensions.add_child();
-        child->set_field(1);
-        child->set_position(position);
-        child->add_child()->set_field(1);
-        child->add_child()->set_field(2);
-    }
-    return dimensions;
-}
-
-FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) {
-    FieldMatcher dimensions;
-    dimensions.set_field(atomId);
-    for (const int field : fields) {
-        dimensions.add_child()->set_field(field);
-    }
-    return dimensions;
-}
-
-void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
-                      const vector<string>& attributionTags) {
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
-}
-
-void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) {
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
-}
-
-std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
-        uint64_t timestampNs, const android::view::DisplayStateEnum state) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
-    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-    AStatsEvent_writeInt32(statsEvent, state);
-
-    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    parseStatsEventToLogEvent(statsEvent, logEvent.get());
-    return logEvent;
-}
-
-std::unique_ptr<LogEvent> CreateScheduledJobStateChangedEvent(
-        const vector<int>& attributionUids, const vector<string>& attributionTags,
-        const string& jobName, const ScheduledJobStateChanged::State state, uint64_t timestampNs) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED);
-    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
-    writeAttribution(statsEvent, attributionUids, attributionTags);
-    AStatsEvent_writeString(statsEvent, jobName.c_str());
-    AStatsEvent_writeInt32(statsEvent, state);
-
-    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    parseStatsEventToLogEvent(statsEvent, logEvent.get());
-    return logEvent;
-}
-
-std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs,
-                                                       const vector<int>& attributionUids,
-                                                       const vector<string>& attributionTags,
-                                                       const string& jobName) {
-    return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName,
-                                               ScheduledJobStateChanged::STARTED, timestampNs);
-}
-
-// Create log event when scheduled job finishes.
-std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs,
-                                                        const vector<int>& attributionUids,
-                                                        const vector<string>& attributionTags,
-                                                        const string& jobName) {
-    return CreateScheduledJobStateChangedEvent(attributionUids, attributionTags, jobName,
-                                               ScheduledJobStateChanged::FINISHED, timestampNs);
-}
-
-std::unique_ptr<LogEvent> CreateSyncStateChangedEvent(uint64_t timestampNs,
-                                                      const vector<int>& attributionUids,
-                                                      const vector<string>& attributionTags,
-                                                      const string& name,
-                                                      const SyncStateChanged::State state) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED);
-    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
-    writeAttribution(statsEvent, attributionUids, attributionTags);
-    AStatsEvent_writeString(statsEvent, name.c_str());
-    AStatsEvent_writeInt32(statsEvent, state);
-
-    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    parseStatsEventToLogEvent(statsEvent, logEvent.get());
-    return logEvent;
-}
-
-std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs,
-                                               const vector<int>& attributionUids,
-                                               const vector<string>& attributionTags,
-                                               const string& name) {
-    return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name,
-                                       SyncStateChanged::ON);
-}
-
-std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs,
-                                             const vector<int>& attributionUids,
-                                             const vector<string>& attributionTags,
-                                             const string& name) {
-    return CreateSyncStateChangedEvent(timestampNs, attributionUids, attributionTags, name,
-                                       SyncStateChanged::OFF);
-}
-
-sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
-                                              const ConfigKey& key) {
-    sp<UidMap> uidMap = new UidMap();
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> periodicAlarmMonitor;
-    sp<StatsLogProcessor> processor = new StatsLogProcessor(
-            uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec * NS_PER_SEC, [](const ConfigKey&) { return true; },
-            [](const int&, const vector<int64_t>&) { return true; },
-            /*logEventFilter=*/nullptr);
-    processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config);
-    return processor;
-}
-
-void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events) {
-  std::sort(events->begin(), events->end(),
-            [](const std::unique_ptr<LogEvent>& a, const std::unique_ptr<LogEvent>& b) {
-              return a->GetElapsedTimestampNs() < b->GetElapsedTimestampNs();
-            });
-}
-
-int64_t StringToId(const string& str) {
-    return static_cast<int64_t>(std::hash<std::string>()(str));
-}
-
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/statsd/benchmark/metric_util.h b/statsd/benchmark/metric_util.h
deleted file mode 100644
index 693bf45..0000000
--- a/statsd/benchmark/metric_util.h
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include "src/stats_log.pb.h"
-#include "src/statsd_config.pb.h"
-#include "src/StatsLogProcessor.h"
-#include "src/logd/LogEvent.h"
-#include "stats_event.h"
-#include "statslog.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-// Create AtomMatcher proto to simply match a specific atom type.
-AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
-
-// Create AtomMatcher proto for scheduled job state changed.
-AtomMatcher CreateScheduledJobStateChangedAtomMatcher();
-
-// Create AtomMatcher proto for starting a scheduled job.
-AtomMatcher CreateStartScheduledJobAtomMatcher();
-
-// Create AtomMatcher proto for a scheduled job is done.
-AtomMatcher CreateFinishScheduledJobAtomMatcher();
-
-// Create AtomMatcher proto for screen brightness state changed.
-AtomMatcher CreateScreenBrightnessChangedAtomMatcher();
-
-// Create AtomMatcher proto for acquiring wakelock.
-AtomMatcher CreateAcquireWakelockAtomMatcher();
-
-// Create AtomMatcher proto for releasing wakelock.
-AtomMatcher CreateReleaseWakelockAtomMatcher() ;
-
-// Create AtomMatcher proto for screen turned on.
-AtomMatcher CreateScreenTurnedOnAtomMatcher();
-
-// Create AtomMatcher proto for screen turned off.
-AtomMatcher CreateScreenTurnedOffAtomMatcher();
-
-// Create AtomMatcher proto for app sync turned on.
-AtomMatcher CreateSyncStartAtomMatcher();
-
-// Create AtomMatcher proto for app sync turned off.
-AtomMatcher CreateSyncEndAtomMatcher();
-
-// Create AtomMatcher proto for app sync moves to background.
-AtomMatcher CreateMoveToBackgroundAtomMatcher();
-
-// Create AtomMatcher proto for app sync moves to foreground.
-AtomMatcher CreateMoveToForegroundAtomMatcher();
-
-// Create Predicate proto for screen is off.
-Predicate CreateScreenIsOffPredicate();
-
-// Create Predicate proto for a running scheduled job.
-Predicate CreateScheduledJobPredicate();
-
-// Create Predicate proto for holding wakelock.
-Predicate CreateHoldingWakelockPredicate();
-
-// Create a Predicate proto for app syncing.
-Predicate CreateIsSyncingPredicate();
-
-// Create a Predicate proto for app is in background.
-Predicate CreateIsInBackgroundPredicate();
-
-// Add a predicate to the predicate combination.
-void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
-
-// Create dimensions from primitive fields.
-FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields);
-
-// Create dimensions by attribution uid and tag.
-FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
-                                                  const std::vector<Position>& positions);
-
-// Create dimensions by attribution uid only.
-FieldMatcher CreateAttributionUidDimensions(const int atomId,
-                                            const std::vector<Position>& positions);
-
-void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
-                      const vector<string>& attributionTags);
-
-void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent);
-
-// Create log event for screen state changed.
-std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
-        uint64_t timestampNs, const android::view::DisplayStateEnum state);
-
-// Create log event when scheduled job starts.
-std::unique_ptr<LogEvent> CreateStartScheduledJobEvent(uint64_t timestampNs,
-                                                       const vector<int>& attributionUids,
-                                                       const vector<string>& attributionTags,
-                                                       const string& jobName);
-
-// Create log event when scheduled job finishes.
-std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs,
-                                                        const vector<int>& attributionUids,
-                                                        const vector<string>& attributionTags,
-                                                        const string& jobName);
-
-// Create log event when the app sync starts.
-std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs,
-                                               const vector<int>& attributionUids,
-                                               const vector<string>& attributionTags,
-                                               const string& name);
-
-// Create log event when the app sync ends.
-std::unique_ptr<LogEvent> CreateSyncEndEvent(uint64_t timestampNs,
-                                             const vector<int>& attributionUids,
-                                             const vector<string>& attributionTags,
-                                             const string& name);
-
-// Create a statsd log event processor upon the start time in seconds, config and key.
-sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const StatsdConfig& config,
-                                              const ConfigKey& key);
-
-// Util function to sort the log events by timestamp.
-void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
-
-int64_t StringToId(const string& str);
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/statsd/benchmark/on_log_event_benchmark.cpp b/statsd/benchmark/on_log_event_benchmark.cpp
new file mode 100644
index 0000000..d3e5709
--- /dev/null
+++ b/statsd/benchmark/on_log_event_benchmark.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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 "benchmark/benchmark.h"
+#include "tests/statsd_test_util.h"
+
+using namespace std;
+namespace android {
+namespace os {
+namespace statsd {
+
+static void BM_OnLogEvent(benchmark::State& state) {
+    StatsdConfig config;
+    auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = wakelockAcquireMatcher;
+
+    *config.add_event_metric() =
+            createEventMetric("Event", wakelockAcquireMatcher.id(), /* condition */ nullopt);
+
+    for (int atomId = 1000; atomId < 2000; atomId++) {
+        auto matcher = CreateSimpleAtomMatcher("name" + to_string(atomId), atomId);
+        *config.add_atom_matcher() = CreateSimpleAtomMatcher("name" + to_string(atomId), atomId);
+        *config.add_event_metric() = createEventMetric("Event" + to_string(atomId), matcher.id(),
+                                                       /* condition */ nullopt);
+    }
+
+    ConfigKey cfgKey;
+    std::vector<std::unique_ptr<LogEvent>> events;
+    vector<int> attributionUids = {111};
+    vector<string> attributionTags = {"App1"};
+    for (int i = 1; i <= 10; i++) {
+        events.push_back(CreateAcquireWakelockEvent(2 + i, attributionUids, attributionTags,
+                                                    "wl" + to_string(i)));
+    }
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(1, 1, config, cfgKey);
+
+    for (auto _ : state) {
+        for (const auto& event : events) {
+            processor->OnLogEvent(event.get());
+        }
+    }
+}
+BENCHMARK(BM_OnLogEvent);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/benchmark/stats_write_benchmark.cpp b/statsd/benchmark/stats_write_benchmark.cpp
index f5a0cd5..b84b0cd 100644
--- a/statsd/benchmark/stats_write_benchmark.cpp
+++ b/statsd/benchmark/stats_write_benchmark.cpp
@@ -13,28 +13,45 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <statslog_statsdtest.h>
+
 #include "benchmark/benchmark.h"
-#include <statslog.h>
 
 namespace android {
 namespace os {
 namespace statsd {
 
-static void BM_StatsWrite(benchmark::State& state) {
-    const char* reason = "test";
-    int64_t boot_end_time = 1234567;
-    int64_t total_duration = 100;
-    int64_t bootloader_duration = 10;
-    int64_t time_since_last_boot = 99999999;
+static void BM_StatsEventObtain(benchmark::State& state) {
     while (state.KeepRunning()) {
-        android::util::stats_write(
-                android::util::BOOT_SEQUENCE_REPORTED, reason, reason,
-                boot_end_time, total_duration, bootloader_duration, time_since_last_boot);
-        total_duration++;
+        AStatsEvent* event = AStatsEvent_obtain();
+        benchmark::DoNotOptimize(event);
+        AStatsEvent_release(event);
+    }
+}
+BENCHMARK(BM_StatsEventObtain);
+
+static void BM_StatsWrite(benchmark::State& state) {
+    int32_t parent_uid = 0;
+    int32_t isolated_uid = 100;
+    int32_t event = 1;
+    while (state.KeepRunning()) {
+        util::stats_write(util::ISOLATED_UID_CHANGED, parent_uid, isolated_uid, event++);
     }
 }
 BENCHMARK(BM_StatsWrite);
 
+static void BM_StatsWriteViaQueue(benchmark::State& state) {
+    // writes dedicated atom which known to be put into the queue for the test purpose
+    int32_t uid = 0;
+    int32_t label = 100;
+    int32_t a_state = 1;
+    while (state.KeepRunning()) {
+        benchmark::DoNotOptimize(
+                util::stats_write(util::APP_BREADCRUMB_REPORTED, uid, label, a_state++));
+    }
+}
+BENCHMARK(BM_StatsWriteViaQueue);
+
 }  //  namespace statsd
 }  //  namespace os
 }  //  namespace android
diff --git a/statsd/benchmark/string_transform_benchmark.cpp b/statsd/benchmark/string_transform_benchmark.cpp
new file mode 100644
index 0000000..0c5f5c6
--- /dev/null
+++ b/statsd/benchmark/string_transform_benchmark.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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 <memory>
+#include <regex>
+#include <string>
+
+#include "benchmark/benchmark.h"
+// #include "re2/re2.h"
+#include "utils/Regex.h"
+
+using android::os::statsd::Regex;
+using namespace std;
+
+static void removeTrailingCharacters(string& str, const string& characters) {
+    str.erase(str.find_last_not_of(characters) + 1, string::npos);
+}
+
+static void removeLeadingCharacters(string& str, const char charToRemove) {
+    str.erase(0, min(str.find_first_not_of(charToRemove), str.size() - 1));
+}
+
+static void removeTrailingNumbers(string& str) {
+    int i = str.length() - 1;
+    while (i >= 0 && isdigit(str[i])) {
+        str.erase(i, 1);
+        i--;
+    }
+}
+
+static void BM_RemoveTrailingCharacters(benchmark::State& state) {
+    const string prefix(state.range(0), 'a' + rand() % 26);
+    const string suffix(state.range(1), '0' + rand() % 10);
+    const string input = prefix + suffix;
+    const string characters("0123456789");
+    for (auto _ : state) {
+        string str = input;
+        removeTrailingCharacters(str, characters);
+        benchmark::DoNotOptimize(str);
+    }
+}
+BENCHMARK(BM_RemoveTrailingCharacters)->RangeMultiplier(2)->RangePair(0, 20, 0, 20);
+
+static void BM_RemoveTrailingNumbers(benchmark::State& state) {
+    const string prefix(state.range(0), 'a' + rand() % 26);
+    const string suffix(state.range(1), '0' + rand() % 10);
+    const string input = prefix + suffix;
+    for (auto _ : state) {
+        string str = input;
+        removeTrailingNumbers(str);
+        benchmark::DoNotOptimize(str);
+    }
+}
+BENCHMARK(BM_RemoveTrailingNumbers)->RangeMultiplier(2)->RangePair(0, 20, 0, 20);
+
+static void BM_RemoveTrailingNumbersCppRegex(benchmark::State& state) {
+    static const regex re(R"([\d]+$)");
+    const string prefix(state.range(0), 'a' + rand() % 26);
+    const string suffix(state.range(1), '0' + rand() % 10);
+    const string input = prefix + suffix;
+    for (auto _ : state) {
+        string str = input;
+        benchmark::DoNotOptimize(regex_replace(str, re, ""));
+    }
+}
+BENCHMARK(BM_RemoveTrailingNumbersCppRegex)->RangeMultiplier(2)->RangePair(0, 20, 0, 20);
+
+static void BM_RemoveTrailingNumbersCRegex(benchmark::State& state) {
+    unique_ptr<Regex> re = Regex::create(R"([0-9]+$)");
+    const string prefix(state.range(0), 'a' + rand() % 26);
+    const string suffix(state.range(1), '0' + rand() % 10);
+    const string input = prefix + suffix;
+    for (auto _ : state) {
+        string str = input;
+        benchmark::DoNotOptimize(re->replace(str, ""));
+    }
+}
+BENCHMARK(BM_RemoveTrailingNumbersCRegex)->RangeMultiplier(2)->RangePair(0, 20, 0, 20);
+
+// To run RE2 benchmark locally, libregex_re2 under external/regex_re2 needs to be made visible to
+// statsd_benchmark.
+// static void BM_RemoveTrailingNumbersRe2(benchmark::State& state) {
+//     using namespace re2;
+//     static const RE2 re2{R"([\d]+$)"};
+//     const string prefix(state.range(0), 'a' + rand() % 26);
+//     const string suffix(state.range(1), '0' + rand() % 10);
+//     const string input = prefix + suffix;
+//     for (auto _ : state) {
+//         string str = input;
+//         RE2::Replace(&str, re2, "");
+//         benchmark::DoNotOptimize(str);
+//     }
+// }
+// BENCHMARK(BM_RemoveTrailingNumbersRe2)->RangeMultiplier(2)->RangePair(0, 20, 0, 20);
diff --git a/statsd/fuzzers/statsd_service_fuzzer.cpp b/statsd/fuzzers/statsd_service_fuzzer.cpp
new file mode 100644
index 0000000..7baca73
--- /dev/null
+++ b/statsd/fuzzers/statsd_service_fuzzer.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "Log.h"
+
+#include <android/binder_interface_utils.h>
+#include <fuzzbinder/libbinder_ndk_driver.h>
+
+#include "StatsService.h"
+#include "packages/UidMap.h"
+
+using namespace android;
+using namespace android::os::statsd;
+using ndk::SharedRefBase;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+    std::shared_ptr<LogEventQueue> eventQueue =
+            std::make_shared<LogEventQueue>(8000 /*buffer limit. Same as StatsD binary*/);
+    sp<UidMap> uidMap = UidMap::getInstance();
+    shared_ptr<StatsService> binder =
+            SharedRefBase::make<StatsService>(uidMap, eventQueue, logEventFilter);
+    fuzzService(binder->asBinder().get(), FuzzedDataProvider(data, size));
+    return 0;
+}
diff --git a/statsd/fuzzers/statsd_socket_data_fuzzer.cpp b/statsd/fuzzers/statsd_socket_data_fuzzer.cpp
new file mode 100644
index 0000000..bfdfd8b
--- /dev/null
+++ b/statsd/fuzzers/statsd_socket_data_fuzzer.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "socket/StatsSocketListener.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void fuzzSocket(const uint8_t* data, size_t size) {
+    LogEventQueue queue(50000);
+    LogEventFilter filter;
+    filter.setFilteringEnabled(false);
+
+    StatsSocketListener::processSocketMessage((const char*)data, size, 0, 0, queue, filter);
+
+    StatsSocketListener::processStatsEventBuffer(data, size, 0, 0, queue, filter);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    using namespace android::os::statsd;
+
+    fuzzSocket(data, size);
+
+    return 0;
+}
diff --git a/statsd/src/FieldValue.cpp b/statsd/src/FieldValue.cpp
index 02a2c5e..84fad92 100644
--- a/statsd/src/FieldValue.cpp
+++ b/statsd/src/FieldValue.cpp
@@ -60,6 +60,17 @@
     return false;
 }
 
+std::vector<Matcher> dedupFieldMatchers(const std::vector<Matcher>& fieldMatchers) {
+    std::vector<Matcher> dedupedFieldMatchers;
+    for (size_t i = 0; i < fieldMatchers.size(); i++) {
+        if (std::find(dedupedFieldMatchers.begin(), dedupedFieldMatchers.end(), fieldMatchers[i]) ==
+            dedupedFieldMatchers.end()) {
+            dedupedFieldMatchers.push_back(fieldMatchers[i]);
+        }
+    }
+    return dedupedFieldMatchers;
+}
+
 void translateFieldMatcher(int tag, const FieldMatcher& matcher, int depth, int* pos, int* mask,
                            std::vector<Matcher>* output) {
     if (depth > kMaxLogDepth) {
@@ -348,28 +359,30 @@
 }
 
 Value& Value::operator=(const Value& that) {
-    type = that.type;
-    switch (type) {
-        case INT:
-            int_value = that.int_value;
-            break;
-        case LONG:
-            long_value = that.long_value;
-            break;
-        case FLOAT:
-            float_value = that.float_value;
-            break;
-        case DOUBLE:
-            double_value = that.double_value;
-            break;
-        case STRING:
-            str_value = that.str_value;
-            break;
-        case STORAGE:
-            storage_value = that.storage_value;
-            break;
-        default:
-            break;
+    if (this != &that) {
+        type = that.type;
+        switch (type) {
+            case INT:
+                int_value = that.int_value;
+                break;
+            case LONG:
+                long_value = that.long_value;
+                break;
+            case FLOAT:
+                float_value = that.float_value;
+                break;
+            case DOUBLE:
+                double_value = that.double_value;
+                break;
+            case STRING:
+                str_value = that.str_value;
+                break;
+            case STORAGE:
+                storage_value = that.storage_value;
+                break;
+            default:
+                break;
+        }
     }
     return *this;
 }
@@ -480,6 +493,7 @@
     return eq;
 }
 
+/* Is dimension_a a subset of dimension_b. */
 bool subsetDimensions(const std::vector<Matcher>& dimension_a,
                       const std::vector<Matcher>& dimension_b) {
     if (dimension_a.size() > dimension_b.size()) {
@@ -490,6 +504,18 @@
         for (size_t j = 0; j < dimension_b.size(); ++j) {
             if (dimension_a[i] == dimension_b[j]) {
                 found = true;
+                break;
+            }
+
+            // Check equality of repeated fields with different positions.
+            // Only position FIRST and LAST are considered subsets of position ALL.
+            if (dimension_b[j].hasAllPositionMatcher() &&
+                (dimension_a[i].hasFirstPositionMatcher() ||
+                 dimension_a[i].hasLastPositionMatcher())) {
+                if (dimension_a[i].isEqualWithoutPositionBits(dimension_b[j])) {
+                    found = true;
+                    break;
+                }
             }
         }
         if (!found) {
diff --git a/statsd/src/FieldValue.h b/statsd/src/FieldValue.h
index 5906942..66b5475 100644
--- a/statsd/src/FieldValue.h
+++ b/statsd/src/FieldValue.h
@@ -16,7 +16,6 @@
 #pragma once
 
 #include "src/statsd_config.pb.h"
-#include "annotations.h"
 
 namespace android {
 namespace os {
@@ -241,7 +240,21 @@
     }
 
     bool hasAllPositionMatcher() const {
-        return mMatcher.getDepth() >= 1 && mMatcher.getRawPosAtDepth(1) == 0;
+        return mMatcher.getDepth() >= 1 && mMatcher.getRawPosAtDepth(1) == 0 &&
+               getRawMaskAtDepth(1) == 0x7f;
+    }
+
+    bool hasFirstPositionMatcher() const {
+        return mMatcher.getDepth() >= 1 && mMatcher.getRawPosAtDepth(1) == 1;
+    }
+
+    bool hasLastPositionMatcher() const {
+        return mMatcher.getDepth() >= 1 && mMatcher.isLastPosMatcher(1);
+    }
+
+    bool isEqualWithoutPositionBits(const Matcher& that) const {
+        return ((mMatcher.getField() & kClearAllPositionMatcherMask) ==
+                (that.getMatcher().getField() & kClearAllPositionMatcherMask));
     }
 
     inline bool operator!=(const Matcher& that) const {
@@ -358,7 +371,6 @@
 class Annotations {
 public:
     Annotations() {
-        setNested(true);  // Nested = true by default
     }
 
     // This enum stores where particular annotations can be found in the
@@ -451,6 +463,8 @@
 /* returns uid if the field is uid field, or -1 if the field is not a uid field */
 int getUidIfExists(const FieldValue& value);
 
+std::vector<Matcher> dedupFieldMatchers(const std::vector<Matcher>& fieldMatchers);
+
 void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output);
 
 bool isAttributionUidField(const Field& field, const Value& value);
diff --git a/statsd/src/HashableDimensionKey.cpp b/statsd/src/HashableDimensionKey.cpp
index 447a731..837d9e9 100644
--- a/statsd/src/HashableDimensionKey.cpp
+++ b/statsd/src/HashableDimensionKey.cpp
@@ -337,16 +337,8 @@
 }
 
 bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const {
-    if (mValues.size() != that.getValues().size()) {
-        return false;
-    }
-    size_t count = mValues.size();
-    for (size_t i = 0; i < count; i++) {
-        if (mValues[i] != (that.getValues())[i]) {
-            return false;
-        }
-    }
-    return true;
+    // according to http://go/cppref/cpp/container/vector/operator_cmp
+    return mValues == that.mValues;
 };
 
 bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const {
diff --git a/statsd/src/HashableDimensionKey.h b/statsd/src/HashableDimensionKey.h
index b5df456..5753d98 100644
--- a/statsd/src/HashableDimensionKey.h
+++ b/statsd/src/HashableDimensionKey.h
@@ -142,7 +142,7 @@
 
 class AtomDimensionKey {
 public:
-    explicit AtomDimensionKey(const int32_t atomTag, const HashableDimensionKey& atomFieldValues)
+    explicit AtomDimensionKey(int32_t atomTag, const HashableDimensionKey& atomFieldValues)
         : mAtomTag(atomTag), mAtomFieldValues(atomFieldValues){};
 
     AtomDimensionKey(){};
@@ -269,22 +269,16 @@
 }  // namespace os
 }  // namespace android
 
-namespace std {
-
-using android::os::statsd::AtomDimensionKey;
-using android::os::statsd::HashableDimensionKey;
-using android::os::statsd::MetricDimensionKey;
-
 template <>
-struct hash<HashableDimensionKey> {
-    std::size_t operator()(const HashableDimensionKey& key) const {
+struct std::hash<android::os::statsd::HashableDimensionKey> {
+    std::size_t operator()(const android::os::statsd::HashableDimensionKey& key) const {
         return hashDimension(key);
     }
 };
 
 template <>
-struct hash<MetricDimensionKey> {
-    std::size_t operator()(const MetricDimensionKey& key) const {
+struct std::hash<android::os::statsd::MetricDimensionKey> {
+    std::size_t operator()(const android::os::statsd::MetricDimensionKey& key) const {
         android::hash_t hash = hashDimension(key.getDimensionKeyInWhat());
         hash = android::JenkinsHashMix(hash, hashDimension(key.getStateValuesKey()));
         return android::JenkinsHashWhiten(hash);
@@ -292,11 +286,10 @@
 };
 
 template <>
-struct hash<AtomDimensionKey> {
-    std::size_t operator()(const AtomDimensionKey& key) const {
+struct std::hash<android::os::statsd::AtomDimensionKey> {
+    std::size_t operator()(const android::os::statsd::AtomDimensionKey& key) const {
         android::hash_t hash = hashDimension(key.getAtomFieldValues());
         hash = android::JenkinsHashMix(hash, key.getAtomTag());
         return android::JenkinsHashWhiten(hash);
     }
 };
-}  // namespace std
diff --git a/statsd/src/StatsLogProcessor.cpp b/statsd/src/StatsLogProcessor.cpp
index 61d32fc..02356e8 100644
--- a/statsd/src/StatsLogProcessor.cpp
+++ b/statsd/src/StatsLogProcessor.cpp
@@ -36,6 +36,7 @@
 #include "stats_util.h"
 #include "statslog_statsd.h"
 #include "storage/StorageManager.h"
+#include "utils/api_tracing.h"
 
 using namespace android;
 using android::base::StringPrintf;
@@ -53,12 +54,16 @@
 namespace os {
 namespace statsd {
 
+using aidl::android::os::IStatsQueryCallback;
+
 // for ConfigMetricsReportList
 const int FIELD_ID_CONFIG_KEY = 1;
 const int FIELD_ID_REPORTS = 2;
 // for ConfigKey
 const int FIELD_ID_UID = 1;
 const int FIELD_ID_ID = 2;
+const int FIELD_ID_REPORT_NUMBER = 3;
+const int FIELD_ID_STATSD_STATS_ID = 4;
 // for ConfigMetricsReport
 // const int FIELD_ID_METRICS = 1; // written in MetricsManager.cpp
 const int FIELD_ID_UID_MAP = 2;
@@ -68,7 +73,8 @@
 const int FIELD_ID_CURRENT_REPORT_WALL_CLOCK_NANOS = 6;
 const int FIELD_ID_DUMP_REPORT_REASON = 8;
 const int FIELD_ID_STRINGS = 9;
-const int FIELD_ID_DATA_CORRUPTED_REASON = 10;
+const int FIELD_ID_DATA_CORRUPTED_REASON = 11;
+const int FIELD_ID_ESTIMATED_DATA_BYTES = 12;
 
 // for ActiveConfigList
 const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1;
@@ -90,14 +96,20 @@
         const sp<AlarmMonitor>& anomalyAlarmMonitor, const sp<AlarmMonitor>& periodicAlarmMonitor,
         const int64_t timeBaseNs, const std::function<bool(const ConfigKey&)>& sendBroadcast,
         const std::function<bool(const int&, const vector<int64_t>&)>& activateBroadcast,
+        const std::function<void(const ConfigKey&, const string&, const vector<int64_t>&)>&
+                sendRestrictedMetricsBroadcast,
         const std::shared_ptr<LogEventFilter>& logEventFilter)
-    : mUidMap(uidMap),
+    : mLastTtlTime(0),
+      mLastFlushRestrictedTime(0),
+      mLastDbGuardrailEnforcementTime(0),
+      mUidMap(uidMap),
       mPullerManager(pullerManager),
       mAnomalyAlarmMonitor(anomalyAlarmMonitor),
       mPeriodicAlarmMonitor(periodicAlarmMonitor),
       mLogEventFilter(logEventFilter),
       mSendBroadcast(sendBroadcast),
       mSendActivationBroadcast(activateBroadcast),
+      mSendRestrictedMetricsBroadcast(sendRestrictedMetricsBroadcast),
       mTimeBaseNs(timeBaseNs),
       mLargestTimestampSeen(0),
       mLastTimestampSeen(0) {
@@ -124,14 +136,14 @@
 }
 
 void StatsLogProcessor::processFiredAnomalyAlarmsLocked(
-        const int64_t& timestampNs,
+        const int64_t timestampNs,
         unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet) {
     for (const auto& itr : mMetricsManagers) {
         itr.second->onAnomalyAlarmFired(timestampNs, alarmSet);
     }
 }
 void StatsLogProcessor::onPeriodicAlarmFired(
-        const int64_t& timestampNs,
+        const int64_t timestampNs,
         unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     for (const auto& itr : mMetricsManagers) {
@@ -378,6 +390,7 @@
 }
 
 void StatsLogProcessor::OnLogEvent(LogEvent* event) {
+    ATRACE_CALL();
     OnLogEvent(event, getElapsedRealtimeNs());
 }
 
@@ -440,16 +453,28 @@
         informAnomalyAlarmFiredLocked(NanoToMillis(elapsedRealtimeNs));
     }
 
-    const int64_t curTimeSec = getElapsedRealtimeSec();
+    const int64_t curTimeSec = NanoToSeconds(elapsedRealtimeNs);
     if (curTimeSec - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) {
         mPullerManager->ClearPullerCacheIfNecessary(curTimeSec * NS_PER_SEC);
         mLastPullerCacheClearTimeSec = curTimeSec;
     }
 
+    flushRestrictedDataIfNecessaryLocked(elapsedRealtimeNs);
+    enforceDataTtlsIfNecessaryLocked(getWallClockNs(), elapsedRealtimeNs);
+    enforceDbGuardrailsIfNecessaryLocked(getWallClockNs(), elapsedRealtimeNs);
+
+    if (!validateAppBreadcrumbEvent(*event)) {
+        return;
+    }
+
     std::unordered_set<int> uidsWithActiveConfigsChanged;
     std::unordered_map<int, std::vector<int64_t>> activeConfigsPerUid;
+
     // pass the event to metrics managers.
     for (auto& pair : mMetricsManagers) {
+        if (event->isRestricted() && !pair.second->hasRestrictedMetricsDelegate()) {
+            continue;
+        }
         int uid = pair.first.GetUid();
         int64_t configId = pair.first.GetId();
         bool isPrevActive = pair.second->isActive();
@@ -533,9 +558,22 @@
 void StatsLogProcessor::OnConfigUpdatedLocked(const int64_t timestampNs, const ConfigKey& key,
                                               const StatsdConfig& config, bool modularUpdate) {
     VLOG("Updated configuration for key %s", key.ToString().c_str());
-    // Create new config if this is not a modular update or if this is a new config.
     const auto& it = mMetricsManagers.find(key);
     bool configValid = false;
+    if (isAtLeastU() && it != mMetricsManagers.end()) {
+        if (it->second->hasRestrictedMetricsDelegate() !=
+            config.has_restricted_metrics_delegate_package_name()) {
+            // Not a modular update if has_restricted_metrics_delegate changes
+            modularUpdate = false;
+        }
+        if (!modularUpdate && it->second->hasRestrictedMetricsDelegate()) {
+            StatsdStats::getInstance().noteDbDeletionConfigUpdated(key);
+            // Always delete the old db if restricted metrics config is not a
+            // modular update.
+            dbutils::deleteDb(key);
+        }
+    }
+    // Create new config if this is not a modular update or if this is a new config.
     if (!modularUpdate || it == mMetricsManagers.end()) {
         sp<MetricsManager> newMetricsManager =
                 new MetricsManager(key, config, mTimeBaseNs, timestampNs, mUidMap, mPullerManager,
@@ -543,8 +581,23 @@
         configValid = newMetricsManager->isConfigValid();
         if (configValid) {
             newMetricsManager->init();
-            mUidMap->OnConfigUpdated(key);
             newMetricsManager->refreshTtl(timestampNs);
+            // Sdk check for U+ is unnecessary because config with restricted metrics delegate
+            // will be invalid on non U+ devices.
+            if (newMetricsManager->hasRestrictedMetricsDelegate()) {
+                mSendRestrictedMetricsBroadcast(key,
+                                                newMetricsManager->getRestrictedMetricsDelegate(),
+                                                newMetricsManager->getAllMetricIds());
+                string err;
+                if (!dbutils::updateDeviceInfoTable(key, err)) {
+                    ALOGE("Failed to create device_info table for configKey %s, err: %s",
+                          key.ToString().c_str(), err.c_str());
+                    StatsdStats::getInstance().noteDeviceInfoTableCreationFailed(key);
+                }
+            } else if (it != mMetricsManagers.end() && it->second->hasRestrictedMetricsDelegate()) {
+                mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(),
+                                                {});
+            }
             mMetricsManagers[key] = newMetricsManager;
             VLOG("StatsdConfig valid");
         }
@@ -552,15 +605,32 @@
         // Preserve the existing MetricsManager, update necessary components and metadata in place.
         configValid = it->second->updateConfig(config, mTimeBaseNs, timestampNs,
                                                mAnomalyAlarmMonitor, mPeriodicAlarmMonitor);
-        if (configValid) {
-            mUidMap->OnConfigUpdated(key);
+        if (configValid && it->second->hasRestrictedMetricsDelegate()) {
+            mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(),
+                                            it->second->getAllMetricIds());
         }
     }
+
+    if (configValid && !config.has_restricted_metrics_delegate_package_name()) {
+        // We do not need to track uid map changes for restricted metrics since the uidmap is not
+        // stored in the sqlite db.
+        mUidMap->OnConfigUpdated(key);
+    } else if (configValid && config.has_restricted_metrics_delegate_package_name()) {
+        mUidMap->OnConfigRemoved(key);
+    }
     if (!configValid) {
         // If there is any error in the config, don't use it.
         // Remove any existing config with the same key.
         ALOGE("StatsdConfig NOT valid");
+        // Send an empty restricted metrics broadcast if the previous config was restricted.
+        if (isAtLeastU() && it != mMetricsManagers.end() &&
+            it->second->hasRestrictedMetricsDelegate()) {
+            mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(), {});
+            StatsdStats::getInstance().noteDbConfigInvalid(key);
+            dbutils::deleteDb(key);
+        }
         mMetricsManagers.erase(key);
+        mUidMap->OnConfigRemoved(key);
     }
 
     updateLogEventFilterLocked();
@@ -576,10 +646,10 @@
     return it->second->byteSize();
 }
 
-void StatsLogProcessor::dumpStates(int out, bool verbose) {
+void StatsLogProcessor::dumpStates(int out, bool verbose) const {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     dprintf(out, "MetricsManager count: %lu\n", (unsigned long)mMetricsManagers.size());
-    for (auto metricsManager : mMetricsManagers) {
+    for (const auto& metricsManager : mMetricsManagers) {
         metricsManager.second->dumpStates(out, verbose);
     }
 }
@@ -594,6 +664,12 @@
                                      const DumpLatency dumpLatency, ProtoOutputStream* proto) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
 
+    auto it = mMetricsManagers.find(key);
+    if (it != mMetricsManagers.end() && it->second->hasRestrictedMetricsDelegate()) {
+        VLOG("Unexpected call to StatsLogProcessor::onDumpReport for restricted metrics.");
+        return;
+    }
+
     // Start of ConfigKey.
     uint64_t configKeyToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY);
     proto->write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid());
@@ -602,7 +678,6 @@
     // End of ConfigKey.
 
     bool keepFile = false;
-    auto it = mMetricsManagers.find(key);
     if (it != mMetricsManagers.end() && it->second->shouldPersistLocalHistory()) {
         keepFile = true;
     }
@@ -628,6 +703,18 @@
     } else {
         ALOGW("Config source %s does not exist", key.ToString().c_str());
     }
+
+    if (erase_data) {
+        ++mDumpReportNumbers[key];
+    }
+    proto->write(FIELD_TYPE_INT32 | FIELD_ID_REPORT_NUMBER, mDumpReportNumbers[key]);
+
+    proto->write(FIELD_TYPE_INT32 | FIELD_ID_STATSD_STATS_ID,
+                 StatsdStats::getInstance().getStatsdStatsId());
+    if (erase_data) {
+        StatsdStats::getInstance().noteMetricsReportSent(key, proto->size(),
+                                                         mDumpReportNumbers[key]);
+    }
 }
 
 /*
@@ -646,8 +733,6 @@
         flushProtoToBuffer(proto, outData);
         VLOG("output data size %zu", outData->size());
     }
-
-    StatsdStats::getInstance().noteMetricsReportSent(key, proto.size());
 }
 
 /*
@@ -676,11 +761,19 @@
     if (it == mMetricsManagers.end()) {
         return;
     }
+    if (it->second->hasRestrictedMetricsDelegate()) {
+        VLOG("Unexpected call to StatsLogProcessor::onConfigMetricsReportLocked for restricted "
+             "metrics.");
+        // Do not call onDumpReport for restricted metrics.
+        return;
+    }
     int64_t lastReportTimeNs = it->second->getLastReportTimeNs();
     int64_t lastReportWallClockNs = it->second->getLastReportWallClockNs();
 
     std::set<string> str_set;
 
+    int64_t totalSize = it->second->byteSize();
+
     ProtoOutputStream tempProto;
     // First, fill in ConfigMetricsReport using current data on memory, which
     // starts from filling in StatsLogReport's.
@@ -717,6 +810,9 @@
     // Data corrupted reason
     writeDataCorruptedReasons(tempProto);
 
+    // Estimated memory bytes
+    tempProto.write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_DATA_BYTES, totalSize);
+
     flushProtoToBuffer(tempProto, buffer);
 
     // save buffer to disk if needed
@@ -766,16 +862,23 @@
     if (it != mMetricsManagers.end()) {
         WriteDataToDiskLocked(key, getElapsedRealtimeNs(), getWallClockNs(), CONFIG_REMOVED,
                               NO_TIME_CONSTRAINTS);
+        if (isAtLeastU() && it->second->hasRestrictedMetricsDelegate()) {
+            StatsdStats::getInstance().noteDbDeletionConfigRemoved(key);
+            dbutils::deleteDb(key);
+            mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(), {});
+        }
         mMetricsManagers.erase(it);
         mUidMap->OnConfigRemoved(key);
     }
     StatsdStats::getInstance().noteConfigRemoved(key);
 
     mLastBroadcastTimes.erase(key);
+    mLastByteSizeTimes.erase(key);
+    mDumpReportNumbers.erase(key);
 
     int uid = key.GetUid();
     bool lastConfigForUid = true;
-    for (auto it : mMetricsManagers) {
+    for (const auto& it : mMetricsManagers) {
         if (it.first.GetUid() == uid) {
             lastConfigForUid = false;
             break;
@@ -792,6 +895,219 @@
     updateLogEventFilterLocked();
 }
 
+// TODO(b/267501143): Add unit tests when metric producer is ready
+void StatsLogProcessor::enforceDataTtlsIfNecessaryLocked(const int64_t wallClockNs,
+                                                         const int64_t elapsedRealtimeNs) {
+    if (!isAtLeastU()) {
+        return;
+    }
+    if (elapsedRealtimeNs - mLastTtlTime < StatsdStats::kMinTtlCheckPeriodNs) {
+        return;
+    }
+    enforceDataTtlsLocked(wallClockNs, elapsedRealtimeNs);
+}
+
+void StatsLogProcessor::flushRestrictedDataIfNecessaryLocked(const int64_t elapsedRealtimeNs) {
+    if (!isAtLeastU()) {
+        return;
+    }
+    if (elapsedRealtimeNs - mLastFlushRestrictedTime < StatsdStats::kMinFlushRestrictedPeriodNs) {
+        return;
+    }
+    flushRestrictedDataLocked(elapsedRealtimeNs);
+}
+
+void StatsLogProcessor::querySql(const string& sqlQuery, const int32_t minSqlClientVersion,
+                                 const optional<vector<uint8_t>>& policyConfig,
+                                 const shared_ptr<IStatsQueryCallback>& callback,
+                                 const int64_t configId, const string& configPackage,
+                                 const int32_t callingUid) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    string err = "";
+
+    if (!isAtLeastU()) {
+        ALOGW("Restricted metrics query invoked on U- device");
+        StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+                configId, configPackage, std::nullopt, callingUid,
+                InvalidQueryReason(FLAG_DISABLED));
+        return;
+    }
+
+    const int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
+
+    // TODO(b/268416460): validate policyConfig here
+
+    if (minSqlClientVersion > dbutils::getDbVersion()) {
+        callback->sendFailure(StringPrintf(
+                "Unsupported sqlite version. Installed Version: %d, Requested Version: %d.",
+                dbutils::getDbVersion(), minSqlClientVersion));
+        StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+                configId, configPackage, std::nullopt, callingUid,
+                InvalidQueryReason(UNSUPPORTED_SQLITE_VERSION));
+        return;
+    }
+
+    set<int32_t> configPackageUids;
+    const auto& uidMapItr = UidMap::sAidToUidMapping.find(configPackage);
+    if (uidMapItr != UidMap::sAidToUidMapping.end()) {
+        configPackageUids.insert(uidMapItr->second);
+    } else {
+        configPackageUids = mUidMap->getAppUid(configPackage);
+    }
+
+    InvalidQueryReason invalidQueryReason;
+    set<ConfigKey> keysToQuery = getRestrictedConfigKeysToQueryLocked(
+            callingUid, configId, configPackageUids, err, invalidQueryReason);
+
+    if (keysToQuery.empty()) {
+        callback->sendFailure(err);
+        StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+                configId, configPackage, std::nullopt, callingUid,
+                InvalidQueryReason(invalidQueryReason));
+        return;
+    }
+
+    if (keysToQuery.size() > 1) {
+        err = "Ambiguous ConfigKey";
+        callback->sendFailure(err);
+        StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+                configId, configPackage, std::nullopt, callingUid,
+                InvalidQueryReason(AMBIGUOUS_CONFIG_KEY));
+        return;
+    }
+
+    flushRestrictedDataLocked(elapsedRealtimeNs);
+    enforceDataTtlsLocked(getWallClockNs(), elapsedRealtimeNs);
+
+    std::vector<std::vector<std::string>> rows;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    if (!dbutils::query(*(keysToQuery.begin()), sqlQuery, rows, columnTypes, columnNames, err)) {
+        callback->sendFailure(StringPrintf("failed to query db %s:", err.c_str()));
+        StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+                configId, configPackage, keysToQuery.begin()->GetUid(), callingUid,
+                InvalidQueryReason(QUERY_FAILURE), err.c_str());
+        return;
+    }
+
+    vector<string> queryData;
+    queryData.reserve(rows.size() * columnNames.size());
+    // TODO(b/268415904): avoid this vector transformation.
+    if (columnNames.size() != columnTypes.size()) {
+        callback->sendFailure("Inconsistent row sizes");
+        StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+                configId, configPackage, keysToQuery.begin()->GetUid(), callingUid,
+                InvalidQueryReason(INCONSISTENT_ROW_SIZE));
+    }
+    for (size_t i = 0; i < rows.size(); ++i) {
+        if (rows[i].size() != columnNames.size()) {
+            callback->sendFailure("Inconsistent row sizes");
+            StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+                    configId, configPackage, keysToQuery.begin()->GetUid(), callingUid,
+                    InvalidQueryReason(INCONSISTENT_ROW_SIZE));
+            return;
+        }
+        queryData.insert(std::end(queryData), std::make_move_iterator(std::begin(rows[i])),
+                         std::make_move_iterator(std::end(rows[i])));
+    }
+    callback->sendResults(queryData, columnNames, columnTypes, rows.size());
+    StatsdStats::getInstance().noteQueryRestrictedMetricSucceed(
+            configId, configPackage, keysToQuery.begin()->GetUid(), callingUid,
+            /*queryLatencyNs=*/getElapsedRealtimeNs() - elapsedRealtimeNs);
+}
+
+set<ConfigKey> StatsLogProcessor::getRestrictedConfigKeysToQueryLocked(
+        const int32_t callingUid, const int64_t configId, const set<int32_t>& configPackageUids,
+        string& err, InvalidQueryReason& invalidQueryReason) {
+    set<ConfigKey> matchedConfigKeys;
+    for (auto uid : configPackageUids) {
+        ConfigKey configKey(uid, configId);
+        if (mMetricsManagers.find(configKey) != mMetricsManagers.end()) {
+            matchedConfigKeys.insert(configKey);
+        }
+    }
+
+    set<ConfigKey> excludedKeys;
+    for (auto& configKey : matchedConfigKeys) {
+        auto it = mMetricsManagers.find(configKey);
+        if (!it->second->validateRestrictedMetricsDelegate(callingUid)) {
+            excludedKeys.insert(configKey);
+        };
+    }
+
+    set<ConfigKey> result;
+    std::set_difference(matchedConfigKeys.begin(), matchedConfigKeys.end(), excludedKeys.begin(),
+                        excludedKeys.end(), std::inserter(result, result.end()));
+    if (matchedConfigKeys.empty()) {
+        err = "No configs found matching the config key";
+        invalidQueryReason = InvalidQueryReason(CONFIG_KEY_NOT_FOUND);
+    } else if (result.empty()) {
+        err = "No matching configs for restricted metrics delegate";
+        invalidQueryReason = InvalidQueryReason(CONFIG_KEY_WITH_UNMATCHED_DELEGATE);
+    }
+
+    return result;
+}
+
+void StatsLogProcessor::EnforceDataTtls(const int64_t wallClockNs,
+                                        const int64_t elapsedRealtimeNs) {
+    if (!isAtLeastU()) {
+        return;
+    }
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    enforceDataTtlsLocked(wallClockNs, elapsedRealtimeNs);
+}
+
+void StatsLogProcessor::enforceDataTtlsLocked(const int64_t wallClockNs,
+                                              const int64_t elapsedRealtimeNs) {
+    for (const auto& itr : mMetricsManagers) {
+        itr.second->enforceRestrictedDataTtls(wallClockNs);
+    }
+    mLastTtlTime = elapsedRealtimeNs;
+}
+
+void StatsLogProcessor::enforceDbGuardrailsIfNecessaryLocked(const int64_t wallClockNs,
+                                                             const int64_t elapsedRealtimeNs) {
+    if (elapsedRealtimeNs - mLastDbGuardrailEnforcementTime <
+        StatsdStats::kMinDbGuardrailEnforcementPeriodNs) {
+        return;
+    }
+    StorageManager::enforceDbGuardrails(STATS_RESTRICTED_DATA_DIR, wallClockNs / NS_PER_SEC,
+                                        StatsdStats::kMaxFileSize);
+    mLastDbGuardrailEnforcementTime = elapsedRealtimeNs;
+}
+
+void StatsLogProcessor::fillRestrictedMetrics(const int64_t configId, const string& configPackage,
+                                              const int32_t delegateUid, vector<int64_t>* output) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+
+    set<int32_t> configPackageUids;
+    const auto& uidMapItr = UidMap::sAidToUidMapping.find(configPackage);
+    if (uidMapItr != UidMap::sAidToUidMapping.end()) {
+        configPackageUids.insert(uidMapItr->second);
+    } else {
+        configPackageUids = mUidMap->getAppUid(configPackage);
+    }
+    string err;
+    InvalidQueryReason invalidQueryReason;
+    set<ConfigKey> keysToGetMetrics = getRestrictedConfigKeysToQueryLocked(
+            delegateUid, configId, configPackageUids, err, invalidQueryReason);
+
+    for (const ConfigKey& key : keysToGetMetrics) {
+        vector<int64_t> metricIds = mMetricsManagers[key]->getAllMetricIds();
+        output->insert(output->end(), metricIds.begin(), metricIds.end());
+    }
+}
+
+void StatsLogProcessor::flushRestrictedDataLocked(const int64_t elapsedRealtimeNs) {
+    for (const auto& it : mMetricsManagers) {
+        // no-op if metricsManager is not restricted
+        it.second->flushRestrictedData();
+    }
+
+    mLastFlushRestrictedTime = elapsedRealtimeNs;
+}
+
 void StatsLogProcessor::flushIfNecessaryLocked(const ConfigKey& key,
                                                MetricsManager& metricsManager) {
     int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
@@ -804,22 +1120,31 @@
 
     // We suspect that the byteSize() computation is expensive, so we set a rate limit.
     size_t totalBytes = metricsManager.byteSize();
+
     mLastByteSizeTimes[key] = elapsedRealtimeNs;
+    const size_t kBytesPerConfig = metricsManager.hasRestrictedMetricsDelegate()
+                                           ? StatsdStats::kBytesPerRestrictedConfigTriggerFlush
+                                           : metricsManager.getTriggerGetDataBytes();
     bool requestDump = false;
     if (totalBytes > metricsManager.getMaxMetricsBytes()) {
         // Too late. We need to start clearing data.
         metricsManager.dropData(elapsedRealtimeNs);
         StatsdStats::getInstance().noteDataDropped(key, totalBytes);
         VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
-    } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) ||
+    } else if ((totalBytes > kBytesPerConfig) ||
                (mOnDiskDataConfigs.find(key) != mOnDiskDataConfigs.end())) {
-        // Request to send a broadcast if:
+        // Request to dump if:
         // 1. in memory data > threshold   OR
         // 2. config has old data report on disk.
         requestDump = true;
     }
 
     if (requestDump) {
+        if (metricsManager.hasRestrictedMetricsDelegate()) {
+            metricsManager.flushRestrictedData();
+            // No need to send broadcast for restricted metrics.
+            return;
+        }
         // Send broadcast so that receivers can pull data.
         auto lastBroadcastTime = mLastBroadcastTimes.find(key);
         if (lastBroadcastTime != mLastBroadcastTimes.end()) {
@@ -846,6 +1171,10 @@
         !mMetricsManagers.find(key)->second->shouldWriteToDisk()) {
         return;
     }
+    if (mMetricsManagers.find(key)->second->hasRestrictedMetricsDelegate()) {
+        mMetricsManagers.find(key)->second->flushRestrictedData();
+        return;
+    }
     vector<uint8_t> buffer;
     onConfigMetricsReportLocked(key, timestampNs, wallClockNs,
                                 true /* include_current_partial_bucket*/, true /* erase_data */,
@@ -1098,7 +1427,7 @@
     }
 }
 
-void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk,
+void StatsLogProcessor::notifyAppUpgrade(const int64_t eventTimeNs, const string& apk,
                                          const int uid, const int64_t version) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     VLOG("Received app upgrade");
@@ -1108,7 +1437,7 @@
     }
 }
 
-void StatsLogProcessor::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
+void StatsLogProcessor::notifyAppRemoved(const int64_t eventTimeNs, const string& apk,
                                          const int uid) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     VLOG("Received app removed");
@@ -1118,7 +1447,7 @@
     }
 }
 
-void StatsLogProcessor::onUidMapReceived(const int64_t& eventTimeNs) {
+void StatsLogProcessor::onUidMapReceived(const int64_t eventTimeNs) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     VLOG("Received uid map");
     StateManager::getInstance().updateLogSources(mUidMap);
@@ -1127,7 +1456,8 @@
     }
 }
 
-void StatsLogProcessor::onStatsdInitCompleted(const int64_t& elapsedTimeNs) {
+void StatsLogProcessor::onStatsdInitCompleted(const int64_t elapsedTimeNs) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     VLOG("Received boot completed signal");
     for (const auto& it : mMetricsManagers) {
@@ -1167,17 +1497,14 @@
     // we add also atoms which could be pushed by statsd itself to simplify the logic
     // to handle metric configs update: APP_BREADCRUMB_REPORTED & ANOMALY_DETECTED
     LogEventFilter::AtomIdSet allAtomIds{
-            util::BINARY_PUSH_STATE_CHANGED,  util::DAVEY_OCCURRED,
-            util::ISOLATED_UID_CHANGED,       util::APP_BREADCRUMB_REPORTED,
-            util::WATCHDOG_ROLLBACK_OCCURRED, util::ANOMALY_DETECTED};
+            util::BINARY_PUSH_STATE_CHANGED, util::ISOLATED_UID_CHANGED,
+            util::APP_BREADCRUMB_REPORTED,   util::WATCHDOG_ROLLBACK_OCCURRED,
+            util::ANOMALY_DETECTED,          util::STATS_SOCKET_LOSS_REPORTED};
     return allAtomIds;
 }
 
 void StatsLogProcessor::updateLogEventFilterLocked() const {
     VLOG("StatsLogProcessor: Updating allAtomIds");
-    if (!mLogEventFilter) {
-        return;
-    }
     LogEventFilter::AtomIdSet allAtomIds = getDefaultAtomIdSet();
     for (const auto& metricsManager : mMetricsManagers) {
         metricsManager.second->addAllAtomIds(allAtomIds);
@@ -1198,6 +1525,42 @@
     }
 }
 
+bool StatsLogProcessor::validateAppBreadcrumbEvent(const LogEvent& event) const {
+    if (event.GetTagId() == util::APP_BREADCRUMB_REPORTED) {
+        // Check that app breadcrumb reported fields are valid.
+        status_t err = NO_ERROR;
+
+        // Uid is 3rd from last field and must match the caller's uid,
+        // unless that caller is statsd itself (statsd is allowed to spoof uids).
+        const long appHookUid = event.GetLong(event.size() - 2, &err);
+        if (err != NO_ERROR) {
+            VLOG("APP_BREADCRUMB_REPORTED had error when parsing the uid");
+            return false;
+        }
+
+        // Because the uid within the LogEvent may have been mapped from
+        // isolated to host, map the loggerUid similarly before comparing.
+        const int32_t loggerUid = mUidMap->getHostUidOrSelf(event.GetUid());
+        if (loggerUid != appHookUid && loggerUid != AID_STATSD) {
+            VLOG("APP_BREADCRUMB_REPORTED has invalid uid: claimed %ld but caller is %d",
+                 appHookUid, loggerUid);
+            return false;
+        }
+
+        // The state must be from 0,3. This part of code must be manually updated.
+        const long appHookState = event.GetLong(event.size(), &err);
+        if (err != NO_ERROR) {
+            VLOG("APP_BREADCRUMB_REPORTED had error when parsing the state field");
+            return false;
+        } else if (appHookState < 0 || appHookState > 3) {
+            VLOG("APP_BREADCRUMB_REPORTED does not have valid state %ld", appHookState);
+            return false;
+        }
+    }
+
+    return true;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/StatsLogProcessor.h b/statsd/src/StatsLogProcessor.h
index 135c108..b6d66af 100644
--- a/statsd/src/StatsLogProcessor.h
+++ b/statsd/src/StatsLogProcessor.h
@@ -16,40 +16,42 @@
 
 #pragma once
 
+#include <aidl/android/os/BnStatsd.h>
 #include <gtest/gtest_prod.h>
+#include <stdio.h>
+
+#include <unordered_map>
+
 #include "config/ConfigListener.h"
+#include "external/StatsPullerManager.h"
 #include "logd/LogEvent.h"
 #include "metrics/MetricsManager.h"
 #include "packages/UidMap.h"
 #include "socket/LogEventFilter.h"
-#include "external/StatsPullerManager.h"
-
 #include "src/statsd_config.pb.h"
 #include "src/statsd_metadata.pb.h"
 
-#include <stdio.h>
-#include <unordered_map>
-
 namespace android {
 namespace os {
 namespace statsd {
 
-
 class StatsLogProcessor : public ConfigListener, public virtual PackageInfoListener {
 public:
     StatsLogProcessor(
             const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
             const sp<AlarmMonitor>& anomalyAlarmMonitor,
-            const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor, const int64_t timeBaseNs,
+            const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor, int64_t timeBaseNs,
             const std::function<bool(const ConfigKey&)>& sendBroadcast,
             const std::function<bool(const int&, const vector<int64_t>&)>& sendActivationBroadcast,
+            const std::function<void(const ConfigKey&, const string&, const vector<int64_t>&)>&
+                    sendRestrictedMetricsBroadcast,
             const std::shared_ptr<LogEventFilter>& logEventFilter);
 
     virtual ~StatsLogProcessor();
 
     void OnLogEvent(LogEvent* event);
 
-    void OnConfigUpdated(const int64_t timestampNs, const int64_t wallClockNs, const ConfigKey& key,
+    void OnConfigUpdated(const int64_t timestampNs, int64_t wallClockNs, const ConfigKey& key,
                          const StatsdConfig& config, bool modularUpdate = true);
     // For testing only.
     void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
@@ -60,28 +62,28 @@
 
     void GetActiveConfigs(const int uid, vector<int64_t>& outActiveConfigs);
 
-    void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs, const int64_t wallClockNs,
+    void onDumpReport(const ConfigKey& key, int64_t dumpTimeNs, int64_t wallClockNs,
                       const bool include_current_partial_bucket, const bool erase_data,
                       const DumpReportReason dumpReportReason, const DumpLatency dumpLatency,
                       vector<uint8_t>* outData);
-    void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs, const int64_t wallClockNs,
+    void onDumpReport(const ConfigKey& key, int64_t dumpTimeNs, int64_t wallClockNs,
                       const bool include_current_partial_bucket, const bool erase_data,
                       const DumpReportReason dumpReportReason, const DumpLatency dumpLatency,
                       ProtoOutputStream* proto);
     // For testing only.
-    void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs,
+    void onDumpReport(const ConfigKey& key, int64_t dumpTimeNs,
                       const bool include_current_partial_bucket, const bool erase_data,
                       const DumpReportReason dumpReportReason, const DumpLatency dumpLatency,
                       vector<uint8_t>* outData);
 
     /* Tells MetricsManager that the alarms in alarmSet have fired. Modifies periodic alarmSet. */
     void onPeriodicAlarmFired(
-            const int64_t& timestampNs,
+            int64_t timestampNs,
             unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
 
     /* Flushes data to disk. Data on memory will be gone after written to disk. */
     void WriteDataToDisk(const DumpReportReason dumpReportReason, const DumpLatency dumpLatency,
-                         const int64_t elapsedRealtimeNs, const int64_t wallClockNs);
+                         const int64_t elapsedRealtimeNs, int64_t wallClockNs);
 
     /* Persist configs containing metrics with active activations to disk. */
     void SaveActiveConfigsToDisk(int64_t currentTimeNs);
@@ -110,23 +112,26 @@
                           int64_t currentWallClockTimeNs,
                           int64_t systemElapsedTimeNs);
 
+    /* Enforces ttls for restricted metrics */
+    void EnforceDataTtls(const int64_t wallClockNs, int64_t elapsedRealtimeNs);
+
     /* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */
     void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs);
 
     /* Notify all MetricsManagers of app upgrades */
-    void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
-                          const int64_t version) override;
+    void notifyAppUpgrade(int64_t eventTimeNs, const string& apk, int uid,
+                          int64_t version) override;
 
     /* Notify all MetricsManagers of app removals */
-    void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) override;
+    void notifyAppRemoved(int64_t eventTimeNs, const string& apk, int uid) override;
 
     /* Notify all MetricsManagers of uid map snapshots received */
-    void onUidMapReceived(const int64_t& eventTimeNs) override;
+    void onUidMapReceived(int64_t eventTimeNs) override;
 
     /* Notify all metrics managers of boot completed
      * This will force a bucket split when the boot is finished.
      */
-    void onStatsdInitCompleted(const int64_t& elapsedTimeNs);
+    void onStatsdInitCompleted(int64_t elapsedTimeNs);
 
     // Reset all configs.
     void resetConfigs();
@@ -135,7 +140,7 @@
         return mUidMap;
     }
 
-    void dumpStates(int outFd, bool verbose);
+    void dumpStates(int outFd, bool verbose) const;
 
     void informPullAlarmFired(const int64_t timestampNs);
 
@@ -144,12 +149,9 @@
     inline void setPrintLogs(bool enabled) {
         std::lock_guard<std::mutex> lock(mMetricsMutex);
         mPrintAllLogs = enabled;
-
-        if (mLogEventFilter) {
-            // Turning on print logs turns off pushed event filtering to enforce
-            // complete log event buffer parsing
-            mLogEventFilter->setFilteringEnabled(!enabled);
-        }
+        // Turning on print logs turns off pushed event filtering to enforce
+        // complete log event buffer parsing
+        mLogEventFilter->setFilteringEnabled(!enabled);
     }
 
     // Add a specific config key to the possible configs to dump ASAP.
@@ -159,6 +161,14 @@
 
     void cancelAnomalyAlarm();
 
+    void querySql(const string& sqlQuery, const int32_t minSqlClientVersion,
+                  const optional<vector<uint8_t>>& policyConfig,
+                  const shared_ptr<aidl::android::os::IStatsQueryCallback>& callback,
+                  const int64_t configId, const string& configPackage, const int32_t callingUid);
+
+    void fillRestrictedMetrics(const int64_t configId, const string& configPackage,
+                               const int32_t delegateUid, vector<int64_t>* output);
+
     /* Returns pre-defined list of atoms to parse by LogEventFilter */
     static LogEventFilter::AtomIdSet getDefaultAtomIdSet();
 
@@ -189,6 +199,18 @@
     // Tracks when we last checked the bytes consumed for each config key.
     std::unordered_map<ConfigKey, int64_t> mLastByteSizeTimes;
 
+    // Tracks the number of times a config with a specified config key has been dumped.
+    std::unordered_map<ConfigKey, int32_t> mDumpReportNumbers;
+
+    // Tracks when we last checked the ttl for restricted metrics.
+    int64_t mLastTtlTime;
+
+    // Tracks when we last flushed restricted metrics.
+    int64_t mLastFlushRestrictedTime;
+
+    // Tracks when we last checked db guardrails.
+    int64_t mLastDbGuardrailEnforcementTime;
+
     // Tracks which config keys has metric reports on disk
     std::set<ConfigKey> mOnDiskDataConfigs;
 
@@ -226,25 +248,42 @@
                                     metadata::StatsMetadataList* metadataList);
 
     void WriteDataToDiskLocked(const DumpReportReason dumpReportReason,
-                               const DumpLatency dumpLatency, const int64_t elapsedRealtimeNs,
+                               const DumpLatency dumpLatency, int64_t elapsedRealtimeNs,
                                const int64_t wallClockNs);
 
-    void WriteDataToDiskLocked(const ConfigKey& key, const int64_t timestampNs,
-                               const int64_t wallClockNs, const DumpReportReason dumpReportReason,
+    void WriteDataToDiskLocked(const ConfigKey& key, int64_t timestampNs, const int64_t wallClockNs,
+                               const DumpReportReason dumpReportReason,
                                const DumpLatency dumpLatency);
 
     void onConfigMetricsReportLocked(
-            const ConfigKey& key, const int64_t dumpTimeStampNs, const int64_t wallClockNs,
+            const ConfigKey& key, int64_t dumpTimeStampNs, int64_t wallClockNs,
             const bool include_current_partial_bucket, const bool erase_data,
             const DumpReportReason dumpReportReason, const DumpLatency dumpLatency,
             /*if dataSavedToDisk is true, it indicates the caller will write the data to disk
              (e.g., before reboot). So no need to further persist local history.*/
             const bool dataSavedToDisk, vector<uint8_t>* proto);
 
+    /* Check if it is time enforce data ttls for restricted metrics, and if it is, enforce ttls
+     * on all restricted metrics. */
+    void enforceDataTtlsIfNecessaryLocked(const int64_t wallClockNs,
+                                          const int64_t elapsedRealtimeNs);
+
+    // Enforces ttls on all restricted metrics.
+    void enforceDataTtlsLocked(const int64_t wallClockNs, int64_t elapsedRealtimeNs);
+
+    // Enforces that dbs are within guardrail parameters.
+    void enforceDbGuardrailsIfNecessaryLocked(const int64_t wallClockNs,
+                                              const int64_t elapsedRealtimeNs);
+
     /* Check if we should send a broadcast if approaching memory limits and if we're over, we
      * actually delete the data. */
     void flushIfNecessaryLocked(const ConfigKey& key, MetricsManager& metricsManager);
 
+    set<ConfigKey> getRestrictedConfigKeysToQueryLocked(int32_t callingUid, const int64_t configId,
+                                                        const set<int32_t>& configPackageUids,
+                                                        string& err,
+                                                        InvalidQueryReason& invalidQueryReason);
+
     // Maps the isolated uid in the log event to host uid if the log event contains uid fields.
     void mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const;
 
@@ -264,8 +303,8 @@
     // Gets experiment ids on disk for associated train and updates them
     // depending on rollback type. Then writes them back to disk and returns
     // them.
-    std::vector<int64_t> processWatchdogRollbackOccurred(const int32_t rollbackTypeIn,
-                                                          const string& packageName);
+    std::vector<int64_t> processWatchdogRollbackOccurred(int32_t rollbackTypeIn,
+                                                         const string& packageName);
 
     // Reset all configs.
     void resetConfigsLocked(const int64_t timestampNs);
@@ -278,14 +317,20 @@
 
     /* Tells MetricsManager that the alarms in alarmSet have fired. Modifies anomaly alarmSet. */
     void processFiredAnomalyAlarmsLocked(
-            const int64_t& timestampNs,
+            int64_t timestampNs,
             unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
 
+    void flushRestrictedDataLocked(const int64_t elapsedRealtimeNs);
+
+    void flushRestrictedDataIfNecessaryLocked(const int64_t elapsedRealtimeNs);
+
     /* Tells LogEventFilter about atom ids to parse */
     void updateLogEventFilterLocked() const;
 
     void writeDataCorruptedReasons(ProtoOutputStream& proto);
 
+    bool validateAppBreadcrumbEvent(const LogEvent& event) const;
+
     // Function used to send a broadcast so that receiver for the config key can call getData
     // to retrieve the stored data.
     std::function<bool(const ConfigKey& key)> mSendBroadcast;
@@ -294,6 +339,12 @@
     // are currently active.
     std::function<bool(const int& uid, const vector<int64_t>& configIds)> mSendActivationBroadcast;
 
+    // Function used to send a broadcast if necessary so the receiver can be notified of the
+    // restricted metrics for the given config.
+    std::function<void(const ConfigKey& key, const string& delegatePackage,
+                       const vector<int64_t>& restrictedMetricIds)>
+            mSendRestrictedMetricsBroadcast;
+
     const int64_t mTimeBaseNs;
 
     // Largest timestamp of the events that we have processed.
@@ -317,6 +368,7 @@
 
     bool mPrintAllLogs = false;
 
+    friend class StatsLogProcessorTestRestricted;
     FRIEND_TEST(StatsLogProcessorTest, TestOutOfOrderLogs);
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
@@ -333,6 +385,17 @@
     FRIEND_TEST(StatsLogProcessorTest, TestEmptyConfigHasNoUidMap);
     FRIEND_TEST(StatsLogProcessorTest, TestReportIncludesSubConfig);
     FRIEND_TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, TestInconsistentRestrictedMetricsConfigUpdate);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, TestRestrictedLogEventPassed);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, TestRestrictedLogEventNotPassed);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, RestrictedMetricsManagerOnDumpReportNotCalled);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, NonRestrictedMetricsManagerOnDumpReportCalled);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, RestrictedMetricOnDumpReportEmpty);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, NonRestrictedMetricOnDumpReportNotEmpty);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, RestrictedMetricNotWriteToDisk);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, NonRestrictedMetricWriteToDisk);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, RestrictedMetricFlushIfReachMemoryLimit);
+    FRIEND_TEST(StatsLogProcessorTestRestricted, RestrictedMetricNotFlushIfNotReachMemoryLimit);
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration1);
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration2);
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensionsForSumDuration3);
@@ -352,6 +415,19 @@
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsWithActivation);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsNoCondition);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestConditionChangeToTrueSamplePulledEvents);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestEnforceTtlRemovesOldEvents);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestFlagDisabled);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestLogEventsEnforceTtls);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestQueryEnforceTtls);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestLogEventsDoesNotEnforceTtls);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestNotFlushed);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestFlushInWriteDataToDisk);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestFlushPeriodically);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestTTlsEnforceDbGuardrails);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestOnLogEventMalformedDbNameDeleted);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestEnforceDbGuardrails);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestEnforceDbGuardrailsDoesNotDeleteBeforeGuardrail);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestRestrictedMetricLoadsTtlFromDisk);
 
     FRIEND_TEST(AnomalyCountDetectionE2eTest, TestSlicedCountMetric_single_bucket);
     FRIEND_TEST(AnomalyCountDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
@@ -409,10 +485,16 @@
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithValueFieldPositionALL);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithMultipleAggTypes);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithDefaultAggType);
 
     FRIEND_TEST(KllMetricE2eTest, TestInitWithKllFieldPositionALL);
 
     FRIEND_TEST(StatsServiceStatsdInitTest, StatsServiceStatsdInitTest);
+
+    FRIEND_TEST(StringReplaceE2eTest, TestPulledDimension);
+    FRIEND_TEST(StringReplaceE2eTest, TestPulledWhat);
+    FRIEND_TEST(StringReplaceE2eTest, TestMultipleMatchersForAtom);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/StatsService.cpp b/statsd/src/StatsService.cpp
index 6450115..9cbd7b1 100644
--- a/statsd/src/StatsService.cpp
+++ b/statsd/src/StatsService.cpp
@@ -18,28 +18,33 @@
 #include "Log.h"
 
 #include "StatsService.h"
-#include "stats_log_util.h"
-#include "android-base/stringprintf.h"
-#include "config/ConfigKey.h"
-#include "config/ConfigManager.h"
-#include "guardrail/StatsdStats.h"
-#include "storage/StorageManager.h"
-#include "subscriber/SubscriberReporter.h"
 
 #include <android-base/file.h>
 #include <android-base/strings.h>
 #include <android/binder_ibinder_platform.h>
 #include <cutils/multiuser.h>
+#include <private/android_filesystem_config.h>
 #include <src/statsd_config.pb.h>
 #include <src/uid_data.pb.h>
-#include <private/android_filesystem_config.h>
 #include <statslog_statsd.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/random.h>
 #include <sys/system_properties.h>
 #include <unistd.h>
 #include <utils/String16.h>
 
+#include "android-base/stringprintf.h"
+#include "config/ConfigKey.h"
+#include "config/ConfigManager.h"
+#include "flags/FlagProvider.h"
+#include "guardrail/StatsdStats.h"
+#include "stats_log_util.h"
+#include "storage/StorageManager.h"
+#include "subscriber/SubscriberReporter.h"
+#include "utils/DbUtils.h"
+#include "utils/api_tracing.h"
+
 using namespace android;
 
 using android::base::StringPrintf;
@@ -196,6 +201,27 @@
                 VLOG("StatsService::active configs broadcast failed for uid %d", uid);
                 return false;
             },
+            [this](const ConfigKey& key, const string& delegatePackage,
+                   const vector<int64_t>& restrictedMetrics) {
+                set<string> configPackages;
+                set<int32_t> delegateUids;
+                for (const auto& kv : UidMap::sAidToUidMapping) {
+                    if (kv.second == static_cast<uint32_t>(key.GetUid())) {
+                        configPackages.insert(kv.first);
+                    }
+                    if (kv.first == delegatePackage) {
+                        delegateUids.insert(kv.second);
+                    }
+                }
+                if (configPackages.empty()) {
+                    configPackages = mUidMap->getAppNamesFromUid(key.GetUid(), true);
+                }
+                if (delegateUids.empty()) {
+                    delegateUids = mUidMap->getAppUid(delegatePackage);
+                }
+                mConfigManager->SendRestrictedMetricsBroadcast(configPackages, key.GetId(),
+                                                               delegateUids, restrictedMetrics);
+            },
             logEventFilter);
 
     mUidMap->setListener(mProcessor);
@@ -204,12 +230,19 @@
     init_system_properties();
 
     if (mEventQueue != nullptr) {
-        std::thread pushedEventThread([this] { readLogs(); });
-        pushedEventThread.detach();
+        mLogsReaderThread = std::make_unique<std::thread>([this] { readLogs(); });
+        if (mLogsReaderThread) {
+            pthread_setname_np(mLogsReaderThread->native_handle(), "statsd.reader");
+        }
     }
 }
 
 StatsService::~StatsService() {
+    ATRACE_CALL();
+    if (mEventQueue != nullptr) {
+        stopReadingLogs();
+        mLogsReaderThread->join();
+    }
 }
 
 /* Runs on a dedicated thread to process pushed events. */
@@ -218,6 +251,13 @@
     while (1) {
         // Block until an event is available.
         auto event = mEventQueue->waitPop();
+
+        // Below flag will be set when statsd is exiting and log event will be pushed to break
+        // out of waitPop.
+        if (mIsStopRequested) {
+            break;
+        }
+
         // Pass it to StatsLogProcess to all configs/metrics
         // At this point, the LogEventQueue is not blocked, so that the socketListener
         // can read events from the socket and write to buffer to avoid data drop.
@@ -841,8 +881,8 @@
     int32_t state = atoi(args[6].c_str());
     vector<int64_t> experimentIds;
     if (argCount == 8) {
-        vector<string> experimentIdsString = android::base::Split(string(args[7].c_str()), ",");
-        for (string experimentIdString : experimentIdsString) {
+        vector<string> experimentIdsStrings = android::base::Split(string(args[7].c_str()), ",");
+        for (const string& experimentIdString : experimentIdsStrings) {
             int64_t experimentId = strtoll(experimentIdString.c_str(), nullptr, 10);
             experimentIds.push_back(experimentId);
         }
@@ -949,6 +989,7 @@
 }
 
 Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     // Parse fd into proto.
@@ -966,6 +1007,7 @@
 Status StatsService::informOnePackage(const string& app, int32_t uid, int64_t version,
                                       const string& versionString, const string& installer,
                                       const vector<uint8_t>& certificateHash) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informOnePackage was called");
@@ -976,6 +1018,7 @@
 }
 
 Status StatsService::informOnePackageRemoved(const string& app, int32_t uid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informOnePackageRemoved was called");
@@ -991,6 +1034,7 @@
 }
 
 Status StatsService::informAlarmForSubscriberTriggeringFired() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informAlarmForSubscriberTriggeringFired was called");
@@ -1007,6 +1051,7 @@
 }
 
 Status StatsService::informPollAlarmFired() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::informPollAlarmFired was called");
@@ -1016,6 +1061,7 @@
 }
 
 Status StatsService::systemRunning() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     // When system_server is up and running, schedule the dropbox task to run.
@@ -1025,6 +1071,7 @@
 }
 
 Status StatsService::informDeviceShutdown() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     VLOG("StatsService::informDeviceShutdown");
     int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
@@ -1046,6 +1093,7 @@
 }
 
 Status StatsService::statsCompanionReady() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::statsCompanionReady was called");
@@ -1064,6 +1112,7 @@
 }
 
 Status StatsService::bootCompleted() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::bootCompleted was called");
@@ -1087,12 +1136,17 @@
 }
 
 void StatsService::Startup() {
+    ATRACE_CALL();
     mConfigManager->Startup();
+    int64_t wallClockNs = getWallClockNs();
+    int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
     mProcessor->LoadActiveConfigsFromDisk();
-    mProcessor->LoadMetadataFromDisk(getWallClockNs(), getElapsedRealtimeNs());
+    mProcessor->LoadMetadataFromDisk(wallClockNs, elapsedRealtimeNs);
+    mProcessor->EnforceDataTtls(wallClockNs, elapsedRealtimeNs);
 }
 
 void StatsService::Terminate() {
+    ATRACE_CALL();
     ALOGI("StatsService::Terminating");
     if (mProcessor != nullptr) {
         int64_t elapsedRealtimeNs = getElapsedRealtimeNs();
@@ -1113,6 +1167,7 @@
 }
 
 Status StatsService::getData(int64_t key, const int32_t callingUid, vector<uint8_t>* output) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     getDataChecked(key, callingUid, output);
     return Status::ok();
@@ -1120,6 +1175,7 @@
 
 Status StatsService::getDataFd(int64_t key, const int32_t callingUid,
                                const ScopedFileDescriptor& fd) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     vector<uint8_t> reportData;
     getDataChecked(key, callingUid, &reportData);
@@ -1156,6 +1212,7 @@
 }
 
 Status StatsService::getMetadata(vector<uint8_t>* output) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
@@ -1164,6 +1221,7 @@
 
 Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config,
                                       const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     if (addConfigurationChecked(callingUid, key, config)) {
@@ -1187,6 +1245,7 @@
 
 Status StatsService::removeDataFetchOperation(int64_t key,
                                               const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     ConfigKey configKey(callingUid, key);
     mConfigManager->RemoveConfigReceiver(configKey);
@@ -1196,6 +1255,7 @@
 Status StatsService::setDataFetchOperation(int64_t key,
                                            const shared_ptr<IPendingIntentRef>& pir,
                                            const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     ConfigKey configKey(callingUid, key);
@@ -1211,6 +1271,7 @@
 Status StatsService::setActiveConfigsChangedOperation(const shared_ptr<IPendingIntentRef>& pir,
                                                       const int32_t callingUid,
                                                       vector<int64_t>* output) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     mConfigManager->SetActiveConfigsChangedReceiver(callingUid, pir);
@@ -1223,6 +1284,7 @@
 }
 
 Status StatsService::removeActiveConfigsChangedOperation(const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     mConfigManager->RemoveActiveConfigsChangedReceiver(callingUid);
@@ -1230,6 +1292,7 @@
 }
 
 Status StatsService::removeConfiguration(int64_t key, const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     ConfigKey configKey(callingUid, key);
@@ -1241,6 +1304,7 @@
                                             int64_t subscriberId,
                                             const shared_ptr<IPendingIntentRef>& pir,
                                             const int32_t callingUid) {
+    ATRACE_CALL();
     VLOG("StatsService::setBroadcastSubscriber called.");
     ENFORCE_UID(AID_SYSTEM);
 
@@ -1258,6 +1322,7 @@
 Status StatsService::unsetBroadcastSubscriber(int64_t configId,
                                               int64_t subscriberId,
                                               const int32_t callingUid) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::unsetBroadcastSubscriber called.");
@@ -1268,6 +1333,7 @@
 }
 
 Status StatsService::allPullersFromBootRegistered() {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::allPullersFromBootRegistered was called");
@@ -1279,6 +1345,7 @@
                                               int64_t timeoutMillis,
                                               const std::vector<int32_t>& additiveFields,
                                               const shared_ptr<IPullAtomCallback>& pullerCallback) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     VLOG("StatsService::registerPullAtomCallback called.");
     mPullerManager->RegisterPullAtomCallback(uid, atomTag, MillisToNano(coolDownMillis),
@@ -1291,6 +1358,7 @@
         int32_t atomTag, int64_t coolDownMillis, int64_t timeoutMillis,
         const std::vector<int32_t>& additiveFields,
         const shared_ptr<IPullAtomCallback>& pullerCallback) {
+    ATRACE_CALL();
     if (!checkPermission(kPermissionRegisterPullAtom)) {
         return exception(
                 EX_SECURITY,
@@ -1306,6 +1374,7 @@
 }
 
 Status StatsService::unregisterPullAtomCallback(int32_t uid, int32_t atomTag) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     VLOG("StatsService::unregisterPullAtomCallback called.");
     mPullerManager->UnregisterPullAtomCallback(uid, atomTag);
@@ -1313,6 +1382,7 @@
 }
 
 Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) {
+    ATRACE_CALL();
     if (!checkPermission(kPermissionRegisterPullAtom)) {
         return exception(
                 EX_SECURITY,
@@ -1326,6 +1396,7 @@
 }
 
 Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) {
+    ATRACE_CALL();
     ENFORCE_UID(AID_SYSTEM);
     // TODO: add verifier permission
 
@@ -1355,6 +1426,7 @@
 }
 
 void StatsService::statsCompanionServiceDied(void* cookie) {
+    ATRACE_CALL();
     auto thiz = static_cast<StatsService*>(cookie);
     thiz->statsCompanionServiceDiedImpl();
 }
@@ -1388,8 +1460,61 @@
     mPullerManager->SetStatsCompanionService(nullptr);
 }
 
+Status StatsService::setRestrictedMetricsChangedOperation(const int64_t configId,
+                                                          const string& configPackage,
+                                                          const shared_ptr<IPendingIntentRef>& pir,
+                                                          const int32_t callingUid,
+                                                          vector<int64_t>* output) {
+    ATRACE_CALL();
+    ENFORCE_UID(AID_SYSTEM);
+    if (!isAtLeastU()) {
+        ALOGW("setRestrictedMetricsChangedOperation invoked on U- device");
+        return Status::ok();
+    }
+    mConfigManager->SetRestrictedMetricsChangedReceiver(configPackage, configId, callingUid, pir);
+    if (output != nullptr) {
+        mProcessor->fillRestrictedMetrics(configId, configPackage, callingUid, output);
+    } else {
+        ALOGW("StatsService::setRestrictedMetricsChangedOperation output was nullptr");
+    }
+    return Status::ok();
+}
+
+Status StatsService::removeRestrictedMetricsChangedOperation(const int64_t configId,
+                                                             const string& configPackage,
+                                                             const int32_t callingUid) {
+    ATRACE_CALL();
+    ENFORCE_UID(AID_SYSTEM);
+    if (!isAtLeastU()) {
+        ALOGW("removeRestrictedMetricsChangedOperation invoked on U- device");
+        return Status::ok();
+    }
+    mConfigManager->RemoveRestrictedMetricsChangedReceiver(configPackage, configId, callingUid);
+    return Status::ok();
+}
+
+Status StatsService::querySql(const string& sqlQuery, const int32_t minSqlClientVersion,
+                              const optional<vector<uint8_t>>& policyConfig,
+                              const shared_ptr<IStatsQueryCallback>& callback,
+                              const int64_t configKey, const string& configPackage,
+                              const int32_t callingUid) {
+    ATRACE_CALL();
+    ENFORCE_UID(AID_SYSTEM);
+    if (callback == nullptr) {
+        ALOGW("querySql called with null callback.");
+        StatsdStats::getInstance().noteQueryRestrictedMetricFailed(
+                configKey, configPackage, std::nullopt, callingUid,
+                InvalidQueryReason(NULL_CALLBACK));
+        return Status::ok();
+    }
+    mProcessor->querySql(sqlQuery, minSqlClientVersion, policyConfig, callback, configKey,
+                         configPackage, callingUid);
+    return Status::ok();
+}
+
 Status StatsService::addSubscription(const vector<uint8_t>& subscriptionConfig,
                                      const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    ATRACE_CALL();
     ENFORCE_SID(kTracedProbesSid);
 
     initShellSubscriber();
@@ -1399,6 +1524,7 @@
 }
 
 Status StatsService::removeSubscription(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    ATRACE_CALL();
     ENFORCE_SID(kTracedProbesSid);
 
     if (mShellSubscriber != nullptr) {
@@ -1408,6 +1534,7 @@
 }
 
 Status StatsService::flushSubscription(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    ATRACE_CALL();
     ENFORCE_SID(kTracedProbesSid);
 
     if (mShellSubscriber != nullptr) {
@@ -1423,6 +1550,14 @@
     }
 }
 
+void StatsService::stopReadingLogs() {
+    mIsStopRequested = true;
+    // Push this event so that readLogs will process and break out of the loop
+    // after the stop is requested.
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    mEventQueue->push(std::move(logEvent));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/StatsService.h b/statsd/src/StatsService.h
index fbc687c..0a167dd 100644
--- a/statsd/src/StatsService.h
+++ b/statsd/src/StatsService.h
@@ -46,6 +46,7 @@
 using aidl::android::os::BnStatsd;
 using aidl::android::os::IPendingIntentRef;
 using aidl::android::os::IPullAtomCallback;
+using aidl::android::os::IStatsQueryCallback;
 using aidl::android::os::IStatsSubscriptionCallback;
 using aidl::android::util::PropertyParcel;
 using ::ndk::ScopedAIBinder_DeathRecipient;
@@ -145,7 +146,7 @@
     /**
      * Binder call to remove the active configs changed operation for the specified package..
      */
-    virtual Status removeActiveConfigsChangedOperation(const int32_t callingUid) override;
+    virtual Status removeActiveConfigsChangedOperation(int32_t callingUid) override;
     /**
      * Binder call to allow clients to remove the specified configuration.
      */
@@ -212,6 +213,33 @@
     virtual Status updateProperties(const vector<PropertyParcel>& properties);
 
     /**
+     * Binder call to let clients register the restricted metrics changed operation for the given
+     * config and calling uid.
+     */
+    virtual Status setRestrictedMetricsChangedOperation(const int64_t configKey,
+                                                        const string& configPackage,
+                                                        const shared_ptr<IPendingIntentRef>& pir,
+                                                        const int32_t callingUid,
+                                                        vector<int64_t>* output);
+
+    /**
+     * Binder call to remove the restricted metrics changed operation for the specified config
+     * and calling uid.
+     */
+    virtual Status removeRestrictedMetricsChangedOperation(const int64_t configKey,
+                                                           const string& configPackage,
+                                                           const int32_t callingUid);
+
+    /**
+     * Binder call to query data in statsd sql store.
+     */
+    virtual Status querySql(const string& sqlQuery, const int32_t minSqlClientVersion,
+                            const optional<vector<uint8_t>>& policyConfig,
+                            const shared_ptr<IStatsQueryCallback>& callback,
+                            const int64_t configKey, const string& configPackage,
+                            const int32_t callingUid);
+
+    /**
      * Binder call to add a subscription.
      */
     virtual Status addSubscription(const vector<uint8_t>& subscriptionConfig,
@@ -387,6 +415,13 @@
      */
     void onStatsdInitCompleted();
 
+    /*
+     *  This method is used to stop log reader thread.
+     */
+    void stopReadingLogs();
+
+    std::atomic<bool> mIsStopRequested = false;
+
     /**
      * Tracks the uid <--> package name mapping.
      */
@@ -431,6 +466,8 @@
     shared_ptr<LogEventQueue> mEventQueue;
     std::shared_ptr<LogEventFilter> mLogEventFilter;
 
+    std::unique_ptr<std::thread> mLogsReaderThread;
+
     MultiConditionTrigger mBootCompleteTrigger;
     static const inline string kBootCompleteTag = "BOOT_COMPLETE";
     static const inline string kUidMapReceivedTag = "UID_MAP";
@@ -442,6 +479,7 @@
 
     friend class StatsServiceConfigTest;
     friend class StatsServiceStatsdInitTest;
+    friend class RestrictedConfigE2ETest;
 
     FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
     FRIEND_TEST(StatsServiceTest, TestAddConfig_simple);
@@ -461,7 +499,10 @@
     FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket);
     FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket);
     FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitByDefault);
-
+    FRIEND_TEST(RestrictedConfigE2ETest, NonRestrictedConfigGetReport);
+    FRIEND_TEST(RestrictedConfigE2ETest, RestrictedConfigNoReport);
+    FRIEND_TEST(RestrictedConfigE2ETest,
+                TestSendRestrictedMetricsChangedBroadcastMultipleMatchedConfigs);
     FRIEND_TEST(ConfigUpdateE2eTest, TestAnomalyDurationMetric);
 
     FRIEND_TEST(AnomalyDurationDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
diff --git a/statsd/src/annotations.h b/statsd/src/annotations.h
deleted file mode 100644
index cf7f543..0000000
--- a/statsd/src/annotations.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-namespace android {
-namespace os {
-namespace statsd {
-
-const uint8_t ANNOTATION_ID_IS_UID = 1;
-const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
-const uint8_t ANNOTATION_ID_PRIMARY_FIELD = 3;
-const uint8_t ANNOTATION_ID_EXCLUSIVE_STATE = 4;
-const uint8_t ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID = 5;
-const uint8_t ANNOTATION_ID_TRIGGER_STATE_RESET = 7;
-const uint8_t ANNOTATION_ID_STATE_NESTED = 8;
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/statsd/src/anomaly/AlarmMonitor.cpp b/statsd/src/anomaly/AlarmMonitor.cpp
index 4822b54..6359c5f 100644
--- a/statsd/src/anomaly/AlarmMonitor.cpp
+++ b/statsd/src/anomaly/AlarmMonitor.cpp
@@ -36,7 +36,7 @@
 AlarmMonitor::~AlarmMonitor() {}
 
 void AlarmMonitor::setStatsCompanionService(
-        shared_ptr<IStatsCompanionService> statsCompanionService) {
+        const shared_ptr<IStatsCompanionService>& statsCompanionService) {
     std::lock_guard<std::mutex> lock(mLock);
     shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
     mStatsCompanionService = statsCompanionService;
@@ -51,7 +51,7 @@
     }
 }
 
-void AlarmMonitor::add(sp<const InternalAlarm> alarm) {
+void AlarmMonitor::add(const sp<const InternalAlarm>& alarm) {
     std::lock_guard<std::mutex> lock(mLock);
     if (alarm == nullptr) {
         ALOGW("Asked to add a null alarm.");
@@ -71,7 +71,7 @@
     }
 }
 
-void AlarmMonitor::remove(sp<const InternalAlarm> alarm) {
+void AlarmMonitor::remove(const sp<const InternalAlarm>& alarm) {
     std::lock_guard<std::mutex> lock(mLock);
     if (alarm == nullptr) {
         ALOGW("Asked to remove a null alarm.");
diff --git a/statsd/src/anomaly/AlarmMonitor.h b/statsd/src/anomaly/AlarmMonitor.h
index 5c34e38..ee955c9 100644
--- a/statsd/src/anomaly/AlarmMonitor.h
+++ b/statsd/src/anomaly/AlarmMonitor.h
@@ -49,7 +49,7 @@
 
     /** InternalAlarm a is smaller (higher priority) than b if its timestamp is sooner. */
     struct SmallerTimestamp {
-        bool operator()(sp<const InternalAlarm> a, sp<const InternalAlarm> b) const {
+        bool operator()(const sp<const InternalAlarm>& a, const sp<const InternalAlarm>& b) const {
             return (a->timestampSec < b->timestampSec);
         }
     };
@@ -77,19 +77,19 @@
      * If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't
      * update IStatsCompanionService (until such time as it is set non-null).
      */
-    void setStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService);
+    void setStatsCompanionService(const shared_ptr<IStatsCompanionService>& statsCompanionService);
 
     /**
      * Adds the given alarm (reference) to the queue.
      */
-    void add(sp<const InternalAlarm> alarm);
+    void add(const sp<const InternalAlarm>& alarm);
 
     /**
      * Removes the given alarm (reference) from the queue.
      * Note that alarm comparison is reference-based; if another alarm exists
      * with the same timestampSec, that alarm will still remain in the queue.
      */
-    void remove(sp<const InternalAlarm> alarm);
+    void remove(const sp<const InternalAlarm>& alarm);
 
     /**
      * Returns and removes all alarms whose timestamp <= the given timestampSec.
diff --git a/statsd/src/anomaly/AlarmTracker.cpp b/statsd/src/anomaly/AlarmTracker.cpp
index e8c70fa..9d1aa10 100644
--- a/statsd/src/anomaly/AlarmTracker.cpp
+++ b/statsd/src/anomaly/AlarmTracker.cpp
@@ -69,14 +69,20 @@
 }
 
 void AlarmTracker::informAlarmsFired(
-        const int64_t& timestampNs,
+        const int64_t timestampNs,
         unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) {
     if (firedAlarms.empty() || mInternalAlarm == nullptr ||
         firedAlarms.find(mInternalAlarm) == firedAlarms.end()) {
         return;
     }
-    if (!mSubscriptions.empty()) {
-        VLOG("AlarmTracker triggers the subscribers.");
+    if (!mSubscriptions.empty() &&
+        (mAlarmConfig.probability_of_informing() >= 1 ||
+         (mAlarmConfig.probability_of_informing() < 1 &&
+          ((float)rand() / (float)RAND_MAX) < mAlarmConfig.probability_of_informing()))) {
+        // Note that due to float imprecision, 0.0 and 1.0 might not truly mean never/always.
+        // The config writer was advised to use -0.1 and 1.1 for never/always.
+
+        ALOGI("Fate decided that an alarm will trigger subscribers.");
         triggerSubscribers(mAlarmConfig.id(), 0 /*metricId N/A*/, DEFAULT_METRIC_DIMENSION_KEY,
                            0 /* metricValue N/A */, mConfigKey, mSubscriptions);
     }
diff --git a/statsd/src/anomaly/AlarmTracker.h b/statsd/src/anomaly/AlarmTracker.h
index 4b8fab3..c89099f 100644
--- a/statsd/src/anomaly/AlarmTracker.h
+++ b/statsd/src/anomaly/AlarmTracker.h
@@ -42,7 +42,8 @@
 
     void addSubscription(const Subscription& subscription);
 
-    void informAlarmsFired(const int64_t& timestampNs,
+    void informAlarmsFired(
+            int64_t timestampNs,
             unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms);
 
 protected:
diff --git a/statsd/src/anomaly/AnomalyTracker.cpp b/statsd/src/anomaly/AnomalyTracker.cpp
index fe390ca..2829bff 100644
--- a/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/statsd/src/anomaly/AnomalyTracker.cpp
@@ -63,7 +63,7 @@
     return bucketNum % mNumOfPastBuckets;
 }
 
-void AnomalyTracker::advanceMostRecentBucketTo(const int64_t& bucketNum) {
+void AnomalyTracker::advanceMostRecentBucketTo(const int64_t bucketNum) {
     VLOG("advanceMostRecentBucketTo() called.");
     if (mNumOfPastBuckets <= 0) {
         return;
@@ -89,9 +89,8 @@
     mMostRecentBucketNum = bucketNum;
 }
 
-void AnomalyTracker::addPastBucket(const MetricDimensionKey& key,
-                                   const int64_t& bucketValue,
-                                   const int64_t& bucketNum) {
+void AnomalyTracker::addPastBucket(const MetricDimensionKey& key, const int64_t bucketValue,
+                                   const int64_t bucketNum) {
     VLOG("addPastBucket(bucketValue) called.");
     if (mNumOfPastBuckets == 0 ||
         bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
@@ -119,8 +118,8 @@
     }
 }
 
-void AnomalyTracker::addPastBucket(std::shared_ptr<DimToValMap> bucket,
-                                   const int64_t& bucketNum) {
+void AnomalyTracker::addPastBucket(const std::shared_ptr<DimToValMap>& bucket,
+                                   const int64_t bucketNum) {
     VLOG("addPastBucket(bucket) called.");
     if (mNumOfPastBuckets == 0 ||
             bucketNum < 0 || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets) {
@@ -147,9 +146,8 @@
     }
 }
 
-
 void AnomalyTracker::subtractValueFromSum(const MetricDimensionKey& key,
-                                          const int64_t& bucketValue) {
+                                          const int64_t bucketValue) {
     auto itr = mSumOverPastBuckets.find(key);
     if (itr == mSumOverPastBuckets.end()) {
         return;
@@ -171,7 +169,7 @@
 }
 
 int64_t AnomalyTracker::getPastBucketValue(const MetricDimensionKey& key,
-                                           const int64_t& bucketNum) const {
+                                           const int64_t bucketNum) const {
     if (bucketNum < 0 || mMostRecentBucketNum < 0
             || bucketNum <= mMostRecentBucketNum - mNumOfPastBuckets
             || bucketNum > mMostRecentBucketNum) {
@@ -194,10 +192,8 @@
     return 0;
 }
 
-bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum,
-                                   const MetricDimensionKey& key,
-                                   const int64_t& currentBucketValue) {
-
+bool AnomalyTracker::detectAnomaly(const int64_t currentBucketNum, const MetricDimensionKey& key,
+                                   const int64_t currentBucketValue) {
     // currentBucketNum should be the next bucket after pastBuckets. If not, advance so that it is.
     if (currentBucketNum > mMostRecentBucketNum + 1) {
         advanceMostRecentBucketTo(currentBucketNum - 1);
@@ -206,7 +202,7 @@
            getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
 }
 
-void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, int64_t metricId,
+void AnomalyTracker::declareAnomaly(const int64_t timestampNs, int64_t metricId,
                                     const MetricDimensionKey& key, int64_t metricValue) {
     // TODO(b/110563466): Why receive timestamp? RefractoryPeriod should always be based on
     // real time right now.
@@ -214,6 +210,19 @@
         VLOG("Skipping anomaly declaration since within refractory period");
         return;
     }
+
+    // TODO(b/110564268): This should also take in the const MetricDimensionKey& key?
+    util::stats_write(util::ANOMALY_DETECTED, mConfigKey.GetUid(), mConfigKey.GetId(), mAlert.id());
+
+    if (mAlert.probability_of_informing() < 1 &&
+        ((float)rand() / (float)RAND_MAX) >= mAlert.probability_of_informing()) {
+        // Note that due to float imprecision, 0.0 and 1.0 might not truly mean never/always.
+        // The config writer was advised to use -0.1 and 1.1 for never/always.
+        ALOGI("Fate decided that an alert will not trigger subscribers or start the refactory "
+              "period countdown.");
+        return;
+    }
+
     if (mAlert.has_refractory_period_secs()) {
         mRefractoryPeriodEndsSec[key] = ((timestampNs + NS_PER_SEC - 1) / NS_PER_SEC) // round up
                                         + mAlert.refractory_period_secs();
@@ -231,22 +240,17 @@
     }
 
     StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.id());
-
-    // TODO(b/110564268): This should also take in the const MetricDimensionKey& key?
-    util::stats_write(util::ANOMALY_DETECTED, mConfigKey.GetUid(),
-                      mConfigKey.GetId(), mAlert.id());
 }
 
-void AnomalyTracker::detectAndDeclareAnomaly(const int64_t& timestampNs,
-                                             const int64_t& currBucketNum, int64_t metricId,
-                                             const MetricDimensionKey& key,
-                                             const int64_t& currentBucketValue) {
+void AnomalyTracker::detectAndDeclareAnomaly(const int64_t timestampNs, const int64_t currBucketNum,
+                                             int64_t metricId, const MetricDimensionKey& key,
+                                             const int64_t currentBucketValue) {
     if (detectAnomaly(currBucketNum, key, currentBucketValue)) {
         declareAnomaly(timestampNs, metricId, key, currentBucketValue);
     }
 }
 
-bool AnomalyTracker::isInRefractoryPeriod(const int64_t& timestampNs,
+bool AnomalyTracker::isInRefractoryPeriod(const int64_t timestampNs,
                                           const MetricDimensionKey& key) const {
     const auto& it = mRefractoryPeriodEndsSec.find(key);
     if (it != mRefractoryPeriodEndsSec.end()) {
diff --git a/statsd/src/anomaly/AnomalyTracker.h b/statsd/src/anomaly/AnomalyTracker.h
index 281c1f6..d57a4b8 100644
--- a/statsd/src/anomaly/AnomalyTracker.h
+++ b/statsd/src/anomaly/AnomalyTracker.h
@@ -34,7 +34,6 @@
 
 using std::optional;
 using std::shared_ptr;
-using std::unordered_map;
 
 // Does NOT allow negative values.
 class AnomalyTracker : public virtual RefBase {
@@ -55,32 +54,30 @@
     // If a bucket for bucketNum already exists, it will be replaced.
     // Also, advances to bucketNum (if not in the past), effectively filling any intervening
     // buckets with 0s.
-    void addPastBucket(std::shared_ptr<DimToValMap> bucket, const int64_t& bucketNum);
+    void addPastBucket(const std::shared_ptr<DimToValMap>& bucket, const int64_t bucketNum);
 
     // Inserts (or replaces) the bucket entry for the given bucketNum at the given key to be the
     // given bucketValue. If the bucket does not exist, it will be created.
     // Also, advances to bucketNum (if not in the past), effectively filling any intervening
     // buckets with 0s.
-    void addPastBucket(const MetricDimensionKey& key, const int64_t& bucketValue,
-                       const int64_t& bucketNum);
+    void addPastBucket(const MetricDimensionKey& key, int64_t bucketValue, int64_t bucketNum);
 
     // Returns true if, based on past buckets plus the new currentBucketValue (which generally
     // represents the partially-filled current bucket), an anomaly has happened.
     // Also advances to currBucketNum-1.
-    bool detectAnomaly(const int64_t& currBucketNum, const MetricDimensionKey& key,
-                       const int64_t& currentBucketValue);
+    bool detectAnomaly(int64_t currBucketNum, const MetricDimensionKey& key,
+                       int64_t currentBucketValue);
 
     // Informs incidentd about the detected alert.
-    void declareAnomaly(const int64_t& timestampNs, int64_t metricId, const MetricDimensionKey& key,
+    void declareAnomaly(int64_t timestampNs, int64_t metricId, const MetricDimensionKey& key,
                         int64_t metricValue);
 
     // Detects if, based on past buckets plus the new currentBucketValue (which generally
     // represents the partially-filled current bucket), an anomaly has happened, and if so,
     // declares an anomaly and informs relevant subscribers.
     // Also advances to currBucketNum-1.
-    void detectAndDeclareAnomaly(const int64_t& timestampNs, const int64_t& currBucketNum,
-                                 int64_t metricId, const MetricDimensionKey& key,
-                                 const int64_t& currentBucketValue);
+    void detectAndDeclareAnomaly(int64_t timestampNs, int64_t currBucketNum, int64_t metricId,
+                                 const MetricDimensionKey& key, int64_t currentBucketValue);
 
     // Init the AlarmMonitor which is shared across anomaly trackers.
     virtual void setAlarmMonitor(const sp<AlarmMonitor>& alarmMonitor) {
@@ -91,7 +88,7 @@
     int64_t getSumOverPastBuckets(const MetricDimensionKey& key) const;
 
     // Returns the value for a past bucket, or 0 if that bucket doesn't exist.
-    int64_t getPastBucketValue(const MetricDimensionKey& key, const int64_t& bucketNum) const;
+    int64_t getPastBucketValue(const MetricDimensionKey& key, int64_t bucketNum) const;
 
     // Returns the anomaly threshold set in the configuration.
     inline int64_t getAnomalyThreshold() const {
@@ -115,14 +112,14 @@
 
     // Sets an alarm for the given timestamp.
     // Replaces previous alarm if one already exists.
-    virtual void startAlarm(const MetricDimensionKey& dimensionKey, const int64_t& eventTime) {
+    virtual void startAlarm(const MetricDimensionKey& dimensionKey, int64_t eventTime) {
         return;  // The base AnomalyTracker class doesn't have alarms.
     }
 
     // Stops the alarm.
     // If it should have already fired, but hasn't yet (e.g. because the AlarmManager is delayed),
     // declare the anomaly now.
-    virtual void stopAlarm(const MetricDimensionKey& dimensionKey, const int64_t& timestampNs) {
+    virtual void stopAlarm(const MetricDimensionKey& dimensionKey, int64_t timestampNs) {
         return;  // The base AnomalyTracker class doesn't have alarms.
     }
 
@@ -133,7 +130,8 @@
 
     // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker,
     // and removes it from firedAlarms. Does NOT remove the alarm from the AlarmMonitor.
-    virtual void informAlarmsFired(const int64_t& timestampNs,
+    virtual void informAlarmsFired(
+            int64_t timestampNs,
             unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) {
         return; // The base AnomalyTracker class doesn't have alarms.
     }
@@ -185,12 +183,12 @@
     // declared for that dimension) ends, in seconds. From this moment and onwards, anomalies
     // can be declared again.
     // Entries may be, but are not guaranteed to be, removed after the period is finished.
-    unordered_map<MetricDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
+    std::unordered_map<MetricDimensionKey, uint32_t> mRefractoryPeriodEndsSec;
 
     // Advances mMostRecentBucketNum to bucketNum, deleting any data that is now too old.
     // Specifically, since it is now too old, removes the data for
     //   [mMostRecentBucketNum - mNumOfPastBuckets + 1, bucketNum - mNumOfPastBuckets].
-    void advanceMostRecentBucketTo(const int64_t& bucketNum);
+    void advanceMostRecentBucketTo(int64_t bucketNum);
 
     // Add the information in the given bucket to mSumOverPastBuckets.
     void addBucketToSum(const shared_ptr<DimToValMap>& bucket);
@@ -200,10 +198,10 @@
     void subtractBucketFromSum(const shared_ptr<DimToValMap>& bucket);
 
     // From mSumOverPastBuckets[key], subtracts bucketValue, removing it if it is now 0.
-    void subtractValueFromSum(const MetricDimensionKey& key, const int64_t& bucketValue);
+    void subtractValueFromSum(const MetricDimensionKey& key, int64_t bucketValue);
 
     // Returns true if in the refractory period, else false.
-    bool isInRefractoryPeriod(const int64_t& timestampNs, const MetricDimensionKey& key) const;
+    bool isInRefractoryPeriod(int64_t timestampNs, const MetricDimensionKey& key) const;
 
     // Calculates the corresponding bucket index within the circular array.
     // Requires bucketNum >= 0.
@@ -217,7 +215,6 @@
 
     FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
     FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
-    FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection);
     FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
     FRIEND_TEST(AnomalyDurationDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
     FRIEND_TEST(AnomalyDurationDetectionE2eTest, TestDurationMetric_SUM_partial_bucket);
diff --git a/statsd/src/anomaly/DurationAnomalyTracker.cpp b/statsd/src/anomaly/DurationAnomalyTracker.cpp
index 66c71ad..82e5bc8 100644
--- a/statsd/src/anomaly/DurationAnomalyTracker.cpp
+++ b/statsd/src/anomaly/DurationAnomalyTracker.cpp
@@ -36,7 +36,7 @@
 }
 
 void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey,
-                                        const int64_t& timestampNs) {
+                                        const int64_t timestampNs) {
     // Alarms are stored in secs. Must round up, since if it fires early, it is ignored completely.
     uint32_t timestampSec = static_cast<uint32_t>((timestampNs -1) / NS_PER_SEC) + 1; // round up
     if (isInRefractoryPeriod(timestampNs, dimensionKey)) {
@@ -57,7 +57,7 @@
 }
 
 void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey,
-                                       const int64_t& timestampNs) {
+                                       const int64_t timestampNs) {
     const auto itr = mAlarms.find(dimensionKey);
     if (itr == mAlarms.end()) {
         return;
@@ -84,16 +84,16 @@
     mAlarms.clear();
 }
 
-void DurationAnomalyTracker::informAlarmsFired(const int64_t& timestampNs,
+void DurationAnomalyTracker::informAlarmsFired(
+        const int64_t timestampNs,
         unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) {
-
     if (firedAlarms.empty() || mAlarms.empty()) return;
     // Find the intersection of firedAlarms and mAlarms.
     // The for loop is inefficient, since it loops over all keys, but that's okay since it is very
     // seldomly called. The alternative would be having InternalAlarms store information about the
     // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that
     // is rarely ever called.
-    unordered_map<MetricDimensionKey, sp<const InternalAlarm>> matchedAlarms;
+    std::unordered_map<MetricDimensionKey, sp<const InternalAlarm>> matchedAlarms;
     for (const auto& kv : mAlarms) {
         if (firedAlarms.count(kv.second) > 0) {
             matchedAlarms.insert({kv.first, kv.second});
diff --git a/statsd/src/anomaly/DurationAnomalyTracker.h b/statsd/src/anomaly/DurationAnomalyTracker.h
index a523782..73e741e 100644
--- a/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -23,8 +23,6 @@
 namespace os {
 namespace statsd {
 
-using std::unordered_map;
-
 class DurationAnomalyTracker : public virtual AnomalyTracker {
 public:
     DurationAnomalyTracker(const Alert& alert, const ConfigKey& configKey,
@@ -34,12 +32,12 @@
 
     // Sets an alarm for the given timestamp.
     // Replaces previous alarm if one already exists.
-    void startAlarm(const MetricDimensionKey& dimensionKey, const int64_t& eventTime) override;
+    void startAlarm(const MetricDimensionKey& dimensionKey, int64_t eventTime) override;
 
     // Stops the alarm.
     // If it should have already fired, but hasn't yet (e.g. because the AlarmManager is delayed),
     // declare the anomaly now.
-    void stopAlarm(const MetricDimensionKey& dimensionKey, const int64_t& timestampNs) override;
+    void stopAlarm(const MetricDimensionKey& dimensionKey, int64_t timestampNs) override;
 
     // Stop all the alarms owned by this tracker. Does not declare any anomalies.
     void cancelAllAlarms() override;
@@ -48,7 +46,8 @@
     // and removes it from firedAlarms. The AlarmMonitor is not informed.
     // Note that this will generally be called from a different thread from the other functions;
     // the caller is responsible for thread safety.
-    void informAlarmsFired(const int64_t& timestampNs,
+    void informAlarmsFired(
+            int64_t timestampNs,
             unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& firedAlarms) override;
 
 protected:
diff --git a/statsd/src/anomaly/subscriber_util.cpp b/statsd/src/anomaly/subscriber_util.cpp
index 34f3320..19d0ea5 100644
--- a/statsd/src/anomaly/subscriber_util.cpp
+++ b/statsd/src/anomaly/subscriber_util.cpp
@@ -17,7 +17,10 @@
 #define STATSD_DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
+#include "subscriber_util.h"
+
 #include "external/Perfetto.h"
+#include "external/Uprobestats.h"
 #include "subscriber/IncidentdReporter.h"
 #include "subscriber/SubscriberReporter.h"
 
@@ -25,8 +28,9 @@
 namespace os {
 namespace statsd {
 
-void triggerSubscribers(int64_t ruleId, int64_t metricId, const MetricDimensionKey& dimensionKey,
-                        int64_t metricValue, const ConfigKey& configKey,
+void triggerSubscribers(const int64_t ruleId, const int64_t metricId,
+                        const MetricDimensionKey& dimensionKey, int64_t metricValue,
+                        const ConfigKey& configKey,
                         const std::vector<Subscription>& subscriptions) {
     VLOG("informSubscribers called.");
     if (subscriptions.empty()) {
@@ -55,6 +59,11 @@
                     ALOGW("Failed to generate perfetto traces.");
                 }
                 break;
+            case Subscription::SubscriberInformationCase::kUprobestatsDetails:
+                if (!StartUprobeStats(subscription.uprobestats_details())) {
+                    ALOGW("Failed to start uprobestats.");
+                }
+                break;
             case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
                 SubscriberReporter::getInstance().alertBroadcastSubscriber(configKey, subscription,
                                                                            dimensionKey);
diff --git a/statsd/src/anomaly/subscriber_util.h b/statsd/src/anomaly/subscriber_util.h
index 4d4c83b..d55581d 100644
--- a/statsd/src/anomaly/subscriber_util.h
+++ b/statsd/src/anomaly/subscriber_util.h
@@ -24,7 +24,7 @@
 namespace os {
 namespace statsd {
 
-void triggerSubscribers(const int64_t ruleId, const int64_t metricId,
+void triggerSubscribers(const int64_t ruleId, int64_t metricId,
                         const MetricDimensionKey& dimensionKey, int64_t metricValue,
                         const ConfigKey& configKey, const std::vector<Subscription>& subscriptions);
 
diff --git a/statsd/src/condition/CombinationConditionTracker.cpp b/statsd/src/condition/CombinationConditionTracker.cpp
index 829ae06..af4a18a 100644
--- a/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/statsd/src/condition/CombinationConditionTracker.cpp
@@ -25,7 +25,7 @@
 using std::unordered_map;
 using std::vector;
 
-CombinationConditionTracker::CombinationConditionTracker(const int64_t& id, const int index,
+CombinationConditionTracker::CombinationConditionTracker(const int64_t id, const int index,
                                                          const uint64_t protoHash)
     : ConditionTracker(id, index, protoHash) {
     VLOG("creating CombinationConditionTracker %lld", (long long)mConditionId);
@@ -38,7 +38,7 @@
 optional<InvalidConfigReason> CombinationConditionTracker::init(
         const vector<Predicate>& allConditionConfig,
         const vector<sp<ConditionTracker>>& allConditionTrackers,
-        const unordered_map<int64_t, int>& conditionIdIndexMap, vector<bool>& stack,
+        const unordered_map<int64_t, int>& conditionIdIndexMap, vector<uint8_t>& stack,
         vector<ConditionState>& conditionCache) {
     VLOG("Combination predicate init() %lld", (long long)mConditionId);
     if (mInitialized) {
@@ -203,7 +203,7 @@
         const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues,
         const std::vector<sp<ConditionTracker>>& mAllConditions,
         std::vector<ConditionState>& nonSlicedConditionCache,
-        std::vector<bool>& conditionChangedCache) {
+        std::vector<uint8_t>& conditionChangedCache) {
     // value is up to date.
     if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) {
         return;
diff --git a/statsd/src/condition/CombinationConditionTracker.h b/statsd/src/condition/CombinationConditionTracker.h
index 82463f0..3ed4a83 100644
--- a/statsd/src/condition/CombinationConditionTracker.h
+++ b/statsd/src/condition/CombinationConditionTracker.h
@@ -26,18 +26,18 @@
 
 class CombinationConditionTracker : public ConditionTracker {
 public:
-    CombinationConditionTracker(const int64_t& id, const int index, const uint64_t protoHash);
+    CombinationConditionTracker(int64_t id, int index, const uint64_t protoHash);
 
     ~CombinationConditionTracker();
 
     optional<InvalidConfigReason> init(
             const std::vector<Predicate>& allConditionConfig,
             const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-            const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack,
-            std::vector<ConditionState>& conditionCache) override;
+            const std::unordered_map<int64_t, int>& conditionIdIndexMap,
+            std::vector<uint8_t>& stack, std::vector<ConditionState>& conditionCache) override;
 
     optional<InvalidConfigReason> onConfigUpdated(
-            const std::vector<Predicate>& allConditionProtos, const int index,
+            const std::vector<Predicate>& allConditionProtos, int index,
             const std::vector<sp<ConditionTracker>>& allConditionTrackers,
             const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
             const std::unordered_map<int64_t, int>& conditionTrackerMap) override;
@@ -46,7 +46,7 @@
                            const std::vector<MatchingState>& eventMatcherValues,
                            const std::vector<sp<ConditionTracker>>& mAllConditions,
                            std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache) override;
+                           std::vector<uint8_t>& changedCache) override;
 
     void isConditionMet(const ConditionKey& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
diff --git a/statsd/src/condition/ConditionTracker.h b/statsd/src/condition/ConditionTracker.h
index 2aa3ff0..b09d350 100644
--- a/statsd/src/condition/ConditionTracker.h
+++ b/statsd/src/condition/ConditionTracker.h
@@ -31,7 +31,7 @@
 
 class ConditionTracker : public virtual RefBase {
 public:
-    ConditionTracker(const int64_t& id, const int index, const uint64_t protoHash)
+    ConditionTracker(int64_t id, int index, const uint64_t protoHash)
         : mConditionId(id),
           mIndex(index),
           mInitialized(false),
@@ -57,8 +57,8 @@
     virtual optional<InvalidConfigReason> init(
             const std::vector<Predicate>& allConditionConfig,
             const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-            const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack,
-            std::vector<ConditionState>& conditionCache) = 0;
+            const std::unordered_map<int64_t, int>& conditionIdIndexMap,
+            std::vector<uint8_t>& stack, std::vector<ConditionState>& conditionCache) = 0;
 
     // Update appropriate state on config updates. Primarily, all indices need to be updated.
     // This predicate and all of its children are guaranteed to be preserved across the update.
@@ -73,7 +73,7 @@
     // conditionTrackerMap: map of condition tracker id to index after the config update.
     // returns whether or not the update is successful.
     virtual optional<InvalidConfigReason> onConfigUpdated(
-            const std::vector<Predicate>& allConditionProtos, const int index,
+            const std::vector<Predicate>& allConditionProtos, int index,
             const std::vector<sp<ConditionTracker>>& allConditionTrackers,
             const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
             const std::unordered_map<int64_t, int>& conditionTrackerMap) {
@@ -95,7 +95,7 @@
                                    const std::vector<MatchingState>& eventMatcherValues,
                                    const std::vector<sp<ConditionTracker>>& mAllConditions,
                                    std::vector<ConditionState>& conditionCache,
-                                   std::vector<bool>& conditionChanged) = 0;
+                                   std::vector<uint8_t>& conditionChanged) = 0;
 
     // Query the condition with parameters.
     // [conditionParameters]: a map from condition name to the HashableDimensionKey to query the
diff --git a/statsd/src/condition/SimpleConditionTracker.cpp b/statsd/src/condition/SimpleConditionTracker.cpp
index ccb2946..5b87f13 100644
--- a/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/statsd/src/condition/SimpleConditionTracker.cpp
@@ -27,7 +27,7 @@
 using std::unordered_map;
 
 SimpleConditionTracker::SimpleConditionTracker(
-        const ConfigKey& key, const int64_t& id, const uint64_t protoHash, const int index,
+        const ConfigKey& key, const int64_t id, const uint64_t protoHash, const int index,
         const SimplePredicate& simplePredicate,
         const unordered_map<int64_t, int>& atomMatchingTrackerMap)
     : ConditionTracker(id, index, protoHash),
@@ -59,7 +59,7 @@
 optional<InvalidConfigReason> SimpleConditionTracker::init(
         const vector<Predicate>& allConditionConfig,
         const vector<sp<ConditionTracker>>& allConditionTrackers,
-        const unordered_map<int64_t, int>& conditionIdIndexMap, vector<bool>& stack,
+        const unordered_map<int64_t, int>& conditionIdIndexMap, vector<uint8_t>& stack,
         vector<ConditionState>& conditionCache) {
     // SimpleConditionTracker does not have dependency on other conditions, thus we just return
     // if the initialization was successful.
@@ -145,7 +145,7 @@
 }
 
 void SimpleConditionTracker::handleStopAll(std::vector<ConditionState>& conditionCache,
-                                           std::vector<bool>& conditionChangedCache) {
+                                           std::vector<uint8_t>& conditionChangedCache) {
     // Unless the default condition is false, and there was nothing started, otherwise we have
     // triggered a condition change.
     conditionChangedCache[mIndex] =
@@ -164,13 +164,13 @@
     conditionCache[mIndex] = ConditionState::kFalse;
 }
 
-bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) {
+bool SimpleConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) const {
     if (!mSliced || mSlicedConditionState.find(newKey) != mSlicedConditionState.end()) {
         // if the condition is not sliced or the key is not new, we are good!
         return false;
     }
     // 1. Report the tuple count if the tuple count > soft limit
-    if (mSlicedConditionState.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+    if (mSlicedConditionState.size() >= StatsdStats::kDimensionKeySizeSoftLimit) {
         size_t newTupleCount = mSlicedConditionState.size() + 1;
         StatsdStats::getInstance().noteConditionDimensionSize(mConfigKey, mConditionId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
@@ -265,12 +265,11 @@
          *conditionChangedCache);
 }
 
-void SimpleConditionTracker::evaluateCondition(
-        const LogEvent& event,
-        const vector<MatchingState>& eventMatcherValues,
-        const vector<sp<ConditionTracker>>& mAllConditions,
-        vector<ConditionState>& conditionCache,
-        vector<bool>& conditionChangedCache) {
+void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
+                                               const vector<MatchingState>& eventMatcherValues,
+                                               const vector<sp<ConditionTracker>>& mAllConditions,
+                                               vector<ConditionState>& conditionCache,
+                                               vector<uint8_t>& conditionChangedCache) {
     if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
         // it has been evaluated.
         VLOG("Yes, already evaluated, %lld %d",
diff --git a/statsd/src/condition/SimpleConditionTracker.h b/statsd/src/condition/SimpleConditionTracker.h
index 4687d8e..8af89cb 100644
--- a/statsd/src/condition/SimpleConditionTracker.h
+++ b/statsd/src/condition/SimpleConditionTracker.h
@@ -29,7 +29,7 @@
 
 class SimpleConditionTracker : public ConditionTracker {
 public:
-    SimpleConditionTracker(const ConfigKey& key, const int64_t& id, const uint64_t protoHash,
+    SimpleConditionTracker(const ConfigKey& key, int64_t id, const uint64_t protoHash,
                            const int index, const SimplePredicate& simplePredicate,
                            const std::unordered_map<int64_t, int>& atomMatchingTrackerMap);
 
@@ -38,11 +38,11 @@
     optional<InvalidConfigReason> init(
             const std::vector<Predicate>& allConditionConfig,
             const std::vector<sp<ConditionTracker>>& allConditionTrackers,
-            const std::unordered_map<int64_t, int>& conditionIdIndexMap, std::vector<bool>& stack,
-            std::vector<ConditionState>& conditionCache) override;
+            const std::unordered_map<int64_t, int>& conditionIdIndexMap,
+            std::vector<uint8_t>& stack, std::vector<ConditionState>& conditionCache) override;
 
     optional<InvalidConfigReason> onConfigUpdated(
-            const std::vector<Predicate>& allConditionProtos, const int index,
+            const std::vector<Predicate>& allConditionProtos, int index,
             const std::vector<sp<ConditionTracker>>& allConditionTrackers,
             const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
             const std::unordered_map<int64_t, int>& conditionTrackerMap) override;
@@ -51,7 +51,7 @@
                            const std::vector<MatchingState>& eventMatcherValues,
                            const std::vector<sp<ConditionTracker>>& mAllConditions,
                            std::vector<ConditionState>& conditionCache,
-                           std::vector<bool>& changedCache) override;
+                           std::vector<uint8_t>& changedCache) override;
 
     void isConditionMet(const ConditionKey& conditionParameters,
                         const std::vector<sp<ConditionTracker>>& allConditions,
@@ -120,12 +120,12 @@
                            const std::unordered_map<int64_t, int>& logTrackerMap);
 
     void handleStopAll(std::vector<ConditionState>& conditionCache,
-                       std::vector<bool>& changedCache);
+                       std::vector<uint8_t>& changedCache);
 
     void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart,
                               ConditionState* conditionCache, bool* changedCache);
 
-    bool hitGuardRail(const HashableDimensionKey& newKey);
+    bool hitGuardRail(const HashableDimensionKey& newKey) const;
 
     void dumpState();
 
diff --git a/statsd/src/config/ConfigKey.cpp b/statsd/src/config/ConfigKey.cpp
index 4a2bd27..3b0b835 100644
--- a/statsd/src/config/ConfigKey.cpp
+++ b/statsd/src/config/ConfigKey.cpp
@@ -26,7 +26,7 @@
 ConfigKey::ConfigKey(const ConfigKey& that) : mId(that.mId), mUid(that.mUid) {
 }
 
-ConfigKey::ConfigKey(int uid, const int64_t& id) : mId(id), mUid(uid) {
+ConfigKey::ConfigKey(int uid, const int64_t id) : mId(id), mUid(uid) {
 }
 
 ConfigKey::~ConfigKey() {
diff --git a/statsd/src/config/ConfigKey.h b/statsd/src/config/ConfigKey.h
index 0e5a7e3..e8df355 100644
--- a/statsd/src/config/ConfigKey.h
+++ b/statsd/src/config/ConfigKey.h
@@ -34,13 +34,13 @@
 public:
     ConfigKey();
     ConfigKey(const ConfigKey& that);
-    ConfigKey(int uid, const int64_t& id);
+    ConfigKey(int uid, int64_t id);
     ~ConfigKey();
 
     inline int GetUid() const {
         return mUid;
     }
-    inline const int64_t& GetId() const {
+    inline int64_t GetId() const {
         return mId;
     }
 
@@ -73,17 +73,10 @@
 
 /**
  * A hash function for ConfigKey so it can be used for unordered_map/set.
- * Unfortunately this has to go in std namespace because C++ is fun!
  */
-namespace std {
-
-using android::os::statsd::ConfigKey;
-
 template <>
-struct hash<ConfigKey> {
-    std::size_t operator()(const ConfigKey& key) const {
+struct std::hash<android::os::statsd::ConfigKey> {
+    std::size_t operator()(const android::os::statsd::ConfigKey& key) const {
         return (7 * key.GetUid()) ^ ((hash<long long>()(key.GetId())));
     }
 };
-
-}  // namespace std
diff --git a/statsd/src/config/ConfigKeyWithPackage.h b/statsd/src/config/ConfigKeyWithPackage.h
new file mode 100644
index 0000000..9cf507b
--- /dev/null
+++ b/statsd/src/config/ConfigKeyWithPackage.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::hash;
+using std::string;
+
+/**
+ * A config key that uses a package name instead of a uid. Generally, ConfigKey which uses a uid
+ * should be used. This is currently only used for restricted metrics changed operation.
+ */
+class ConfigKeyWithPackage {
+public:
+    ConfigKeyWithPackage(const string& package, int64_t id) : mPackage(package), mId(id) {
+    }
+
+    inline string GetPackage() const {
+        return mPackage;
+    }
+    inline int64_t GetId() const {
+        return mId;
+    }
+
+    inline bool operator<(const ConfigKeyWithPackage& that) const {
+        if (mPackage != that.mPackage) {
+            return mPackage < that.mPackage;
+        }
+        return mId < that.mId;
+    };
+
+    inline bool operator==(const ConfigKeyWithPackage& that) const {
+        return mPackage == that.mPackage && mId == that.mId;
+    };
+
+private:
+    string mPackage;
+    int64_t mId;
+};
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/config/ConfigListener.cpp b/statsd/src/config/ConfigListener.cpp
index 21a3f16..f0406fc 100644
--- a/statsd/src/config/ConfigListener.cpp
+++ b/statsd/src/config/ConfigListener.cpp
@@ -20,12 +20,6 @@
 namespace os {
 namespace statsd {
 
-ConfigListener::ConfigListener() {
-}
-
-ConfigListener::~ConfigListener() {
-}
-
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/config/ConfigListener.h b/statsd/src/config/ConfigListener.h
index b29e0be..fbebf82 100644
--- a/statsd/src/config/ConfigListener.h
+++ b/statsd/src/config/ConfigListener.h
@@ -30,13 +30,13 @@
  */
 class ConfigListener : public virtual RefBase {
 public:
-    ConfigListener();
-    virtual ~ConfigListener();
+    ConfigListener() = default;
+    virtual ~ConfigListener() = default;
 
     /**
      * A configuration was added or updated.
      */
-    virtual void OnConfigUpdated(const int64_t timestampNs, const ConfigKey& key,
+    virtual void OnConfigUpdated(int64_t timestampNs, const ConfigKey& key,
                                  const StatsdConfig& config, bool modularUpdate = true) = 0;
 
     /**
diff --git a/statsd/src/config/ConfigManager.cpp b/statsd/src/config/ConfigManager.cpp
index 570cd45..d1f93c7 100644
--- a/statsd/src/config/ConfigManager.cpp
+++ b/statsd/src/config/ConfigManager.cpp
@@ -33,14 +33,14 @@
 namespace os {
 namespace statsd {
 
-using std::pair;
 using std::string;
 using std::vector;
 
+using Status = ::ndk::ScopedAStatus;
+
 #define STATS_SERVICE_DIR "/data/misc/stats-service"
 
 using android::base::StringPrintf;
-using std::unique_ptr;
 
 ConfigManager::ConfigManager() {
 }
@@ -51,8 +51,8 @@
 void ConfigManager::Startup() {
     map<ConfigKey, StatsdConfig> configsFromDisk;
     StorageManager::readConfigFromDisk(configsFromDisk);
-    for (const auto& pair : configsFromDisk) {
-        UpdateConfig(pair.first, pair.second);
+    for (const auto& config : configsFromDisk) {
+        UpdateConfig(config.first, config.second);
     }
 }
 
@@ -152,6 +152,83 @@
     }
 }
 
+void ConfigManager::SetRestrictedMetricsChangedReceiver(const string& configPackage,
+                                                        const int64_t configId,
+                                                        const int32_t callingUid,
+                                                        const shared_ptr<IPendingIntentRef>& pir) {
+    lock_guard<mutex> lock(mMutex);
+    ConfigKeyWithPackage configKey(configPackage, configId);
+    mRestrictedMetricsChangedReceivers[configKey][callingUid] = pir;
+}
+
+void ConfigManager::RemoveRestrictedMetricsChangedReceiver(const string& configPackage,
+                                                           const int64_t configId,
+                                                           const int32_t callingUid) {
+    lock_guard<mutex> lock(mMutex);
+    ConfigKeyWithPackage configKey(configPackage, configId);
+    const auto& it = mRestrictedMetricsChangedReceivers.find(configKey);
+    if (it != mRestrictedMetricsChangedReceivers.end()) {
+        it->second.erase(callingUid);
+        if (it->second.empty()) {
+            mRestrictedMetricsChangedReceivers.erase(it);
+        }
+    }
+}
+
+void ConfigManager::RemoveRestrictedMetricsChangedReceiver(
+        const ConfigKeyWithPackage& key, const int32_t delegateUid,
+        const shared_ptr<IPendingIntentRef>& pir) {
+    lock_guard<mutex> lock(mMutex);
+    const auto& it = mRestrictedMetricsChangedReceivers.find(key);
+    if (it != mRestrictedMetricsChangedReceivers.end()) {
+        const auto& pirIt = it->second.find(delegateUid);
+        if (pirIt != it->second.end() && pirIt->second == pir) {
+            it->second.erase(delegateUid);
+            if (it->second.empty()) {
+                mRestrictedMetricsChangedReceivers.erase(it);
+            }
+        }
+    }
+}
+
+void ConfigManager::SendRestrictedMetricsBroadcast(const set<string>& configPackages,
+                                                   const int64_t configId,
+                                                   const set<int32_t>& delegateUids,
+                                                   const vector<int64_t>& metricIds) {
+    map<ConfigKeyWithPackage, map<int32_t, shared_ptr<IPendingIntentRef>>> intentsToSend;
+    {
+        lock_guard<mutex> lock(mMutex);
+        // Invoke the pending intent for all matching configs, as long as the listening delegates
+        // match the allowed delegate uids specified by the config.
+        for (const string& configPackage : configPackages) {
+            ConfigKeyWithPackage key(configPackage, configId);
+            const auto& it = mRestrictedMetricsChangedReceivers.find(key);
+            if (it != mRestrictedMetricsChangedReceivers.end()) {
+                for (const auto& [delegateUid, pir] : it->second) {
+                    if (delegateUids.find(delegateUid) != delegateUids.end()) {
+                        intentsToSend[key][delegateUid] = pir;
+                    }
+                }
+            }
+        }
+    }
+
+    // Invoke the pending intents without holding the lock.
+    for (const auto& [key, innerMap] : intentsToSend) {
+        for (const auto& [delegateUid, pir] : innerMap) {
+            Status status = pir->sendRestrictedMetricsChangedBroadcast(metricIds);
+            if (status.isOk()) {
+                VLOG("ConfigManager::SendRestrictedMetricsBroadcast succeeded");
+            }
+            if (status.getExceptionCode() == EX_TRANSACTION_FAILED &&
+                status.getStatus() == STATUS_DEAD_OBJECT) {
+                // Must also be called without the lock, since remove will acquire the lock.
+                RemoveRestrictedMetricsChangedReceiver(key, delegateUid, pir);
+            }
+        }
+    }
+}
+
 void ConfigManager::RemoveConfig(const ConfigKey& key) {
     vector<sp<ConfigListener>> broadcastList;
     {
@@ -181,6 +258,7 @@
     StorageManager::deleteSuffixedFiles(STATS_SERVICE_DIR, suffix.c_str());
 }
 
+// TODO(b/xxx): consider removing all receivers associated with this uid.
 void ConfigManager::RemoveConfigs(int uid) {
     vector<ConfigKey> removed;
     vector<sp<ConfigListener>> broadcastList;
diff --git a/statsd/src/config/ConfigManager.h b/statsd/src/config/ConfigManager.h
index 9a0f504..5a213b7 100644
--- a/statsd/src/config/ConfigManager.h
+++ b/statsd/src/config/ConfigManager.h
@@ -16,14 +16,17 @@
 
 #pragma once
 
-#include "config/ConfigKey.h"
-#include "config/ConfigListener.h"
-
 #include <aidl/android/os/IPendingIntentRef.h>
+#include <stdio.h>
+
+#include <map>
 #include <mutex>
+#include <set>
 #include <string>
 
-#include <stdio.h>
+#include "config/ConfigKey.h"
+#include "config/ConfigKeyWithPackage.h"
+#include "config/ConfigListener.h"
 
 using aidl::android::os::IPendingIntentRef;
 using std::shared_ptr;
@@ -113,6 +116,27 @@
                                             const shared_ptr<IPendingIntentRef>& pir);
 
     /**
+     * Sets the pending intent that is notified whenever the list of restricted metrics changes
+     */
+    void SetRestrictedMetricsChangedReceiver(const string& configPackage, int64_t configId,
+                                             const int32_t callingUid,
+                                             const shared_ptr<IPendingIntentRef>& pir);
+
+    /**
+     * Erase any restricted metrics changed pending intents associated with this config key & uid.
+     */
+    void RemoveRestrictedMetricsChangedReceiver(const string& configPackage, int64_t configId,
+                                                const int32_t callingUid);
+
+    /**
+     * Sends a restricted metrics broadcast for the valid config keys and delegate package
+     */
+    void SendRestrictedMetricsBroadcast(const std::set<string>& configPackages,
+                                        const int64_t configId,
+                                        const std::set<int32_t>& delegateUids,
+                                        const std::vector<int64_t>& metricIds);
+
+    /**
      * A configuration was removed.
      *
      * Reports this to listeners.
@@ -166,9 +190,24 @@
     std::map<int, shared_ptr<IPendingIntentRef>> mActiveConfigsChangedReceivers;
 
     /**
+     * Each uid can subscribe up to one receiver for a particular config to receive the restricted
+     * metrics for that config. The receiver is specified as IPendingIntentRef.
+     */
+    std::map<ConfigKeyWithPackage, std::map<int32_t, shared_ptr<IPendingIntentRef>>>
+            mRestrictedMetricsChangedReceivers;
+
+    /**
      * The ConfigListeners that will be told about changes.
      */
     std::vector<sp<ConfigListener>> mListeners;
+
+    /**
+     * Erase the restricted metrics changed pending intents associated with this config key & uid if
+     * it is equal to the provided pending intent.
+     */
+    void RemoveRestrictedMetricsChangedReceiver(const ConfigKeyWithPackage& key,
+                                                const int32_t delegateUid,
+                                                const shared_ptr<IPendingIntentRef>& pir);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/config/ConfigMetadataProvider.h b/statsd/src/config/ConfigMetadataProvider.h
new file mode 100644
index 0000000..451503a
--- /dev/null
+++ b/statsd/src/config/ConfigMetadataProvider.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <utils/RefBase.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class ConfigMetadataProvider : virtual public RefBase {
+public:
+    virtual ~ConfigMetadataProvider() {
+    }
+
+    virtual bool useV2SoftMemoryCalculation() = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/external/PullResultReceiver.h b/statsd/src/external/PullResultReceiver.h
index ceaae80..f2d0a4e 100644
--- a/statsd/src/external/PullResultReceiver.h
+++ b/statsd/src/external/PullResultReceiver.h
@@ -29,8 +29,8 @@
 
 class PullResultReceiver : public BnPullAtomResultReceiver {
 public:
-    PullResultReceiver(function<void(int32_t, bool, const vector<StatsEventParcel>&)>
-                               pullFinishCallback);
+    PullResultReceiver(
+            function<void(int32_t, bool, const vector<StatsEventParcel>&)> pullFinishCallback);
     ~PullResultReceiver();
 
     /**
diff --git a/statsd/src/external/StatsCallbackPuller.cpp b/statsd/src/external/StatsCallbackPuller.cpp
index 7d5d04b..a1c8df2 100644
--- a/statsd/src/external/StatsCallbackPuller.cpp
+++ b/statsd/src/external/StatsCallbackPuller.cpp
@@ -37,7 +37,7 @@
 
 StatsCallbackPuller::StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback,
                                          const int64_t coolDownNs, int64_t timeoutNs,
-                                         const vector<int> additiveFields)
+                                         const vector<int>& additiveFields)
     : StatsPuller(tagId, coolDownNs, timeoutNs, additiveFields), mCallback(callback) {
     VLOG("StatsCallbackPuller created for tag %d", tagId);
 }
diff --git a/statsd/src/external/StatsCallbackPuller.h b/statsd/src/external/StatsCallbackPuller.h
index 43d35fc..65acb00 100644
--- a/statsd/src/external/StatsCallbackPuller.h
+++ b/statsd/src/external/StatsCallbackPuller.h
@@ -29,8 +29,8 @@
 class StatsCallbackPuller : public StatsPuller {
 public:
     explicit StatsCallbackPuller(int tagId, const shared_ptr<IPullAtomCallback>& callback,
-                                 const int64_t coolDownNs, const int64_t timeoutNs,
-                                 const std::vector<int> additiveFields);
+                                 const int64_t coolDownNs, int64_t timeoutNs,
+                                 const std::vector<int>& additiveFields);
 
 private:
     PullErrorCode PullInternal(vector<std::shared_ptr<LogEvent>>* data) override;
diff --git a/statsd/src/external/StatsPuller.cpp b/statsd/src/external/StatsPuller.cpp
index 395e660..e0fce5e 100644
--- a/statsd/src/external/StatsPuller.cpp
+++ b/statsd/src/external/StatsPuller.cpp
@@ -18,10 +18,12 @@
 #include "Log.h"
 
 #include "StatsPuller.h"
+
 #include "StatsPullerManager.h"
 #include "guardrail/StatsdStats.h"
 #include "puller_util.h"
 #include "stats_log_util.h"
+#include "utils/api_tracing.h"
 
 namespace android {
 namespace os {
@@ -33,7 +35,7 @@
 void StatsPuller::SetUidMap(const sp<UidMap>& uidMap) { mUidMap = uidMap; }
 
 StatsPuller::StatsPuller(const int tagId, const int64_t coolDownNs, const int64_t pullTimeoutNs,
-                         const std::vector<int> additiveFields)
+                         const std::vector<int>& additiveFields)
     : mTagId(tagId),
       mPullTimeoutNs(pullTimeoutNs),
       mCoolDownNs(coolDownNs),
@@ -44,6 +46,7 @@
 
 PullErrorCode StatsPuller::Pull(const int64_t eventTimeNs,
                                 std::vector<std::shared_ptr<LogEvent>>* data) {
+    ATRACE_CALL();
     lock_guard<std::mutex> lock(mLock);
     const int64_t elapsedTimeNs = getElapsedRealtimeNs();
     const int64_t systemUptimeMillis = getSystemUptimeMillis();
diff --git a/statsd/src/external/StatsPuller.h b/statsd/src/external/StatsPuller.h
index d8c7eb3..4a38382 100644
--- a/statsd/src/external/StatsPuller.h
+++ b/statsd/src/external/StatsPuller.h
@@ -41,10 +41,9 @@
 
 class StatsPuller : public virtual RefBase {
 public:
-    explicit StatsPuller(const int tagId,
-                         const int64_t coolDownNs = NS_PER_SEC,
+    explicit StatsPuller(const int tagId, int64_t coolDownNs = NS_PER_SEC,
                          const int64_t pullTimeoutNs = StatsdStats::kPullMaxDelayNs,
-                         const std::vector<int> additiveFields = std::vector<int>());
+                         const std::vector<int>& additiveFields = std::vector<int>());
 
     virtual ~StatsPuller() {}
 
@@ -69,7 +68,7 @@
     static void SetUidMap(const sp<UidMap>& uidMap);
 
     virtual void SetStatsCompanionService(
-            shared_ptr<IStatsCompanionService> statsCompanionService) {};
+            const shared_ptr<IStatsCompanionService>& statsCompanionService){};
 
 protected:
     const int mTagId;
diff --git a/statsd/src/external/StatsPullerManager.cpp b/statsd/src/external/StatsPullerManager.cpp
index 8fabcad..bba32b9 100644
--- a/statsd/src/external/StatsPullerManager.cpp
+++ b/statsd/src/external/StatsPullerManager.cpp
@@ -54,12 +54,14 @@
 
 bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
                               vector<shared_ptr<LogEvent>>* data) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     return PullLocked(tagId, configKey, eventTimeNs, data);
 }
 
 bool StatsPullerManager::Pull(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
                               vector<std::shared_ptr<LogEvent>>* data) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     return PullLocked(tagId, uids, eventTimeNs, data);
 }
@@ -135,7 +137,7 @@
 }
 
 void StatsPullerManager::SetStatsCompanionService(
-        shared_ptr<IStatsCompanionService> statsCompanionService) {
+        const shared_ptr<IStatsCompanionService>& statsCompanionService) {
     std::lock_guard<std::mutex> _l(mLock);
     shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
     mStatsCompanionService = statsCompanionService;
@@ -148,8 +150,8 @@
 }
 
 void StatsPullerManager::RegisterReceiver(int tagId, const ConfigKey& configKey,
-                                          wp<PullDataReceiver> receiver, int64_t nextPullTimeNs,
-                                          int64_t intervalNs) {
+                                          const wp<PullDataReceiver>& receiver,
+                                          int64_t nextPullTimeNs, int64_t intervalNs) {
     std::lock_guard<std::mutex> _l(mLock);
     auto& receivers = mReceivers[{.atomTag = tagId, .configKey = configKey}];
     for (auto it = receivers.begin(); it != receivers.end(); it++) {
@@ -184,7 +186,7 @@
 }
 
 void StatsPullerManager::UnRegisterReceiver(int tagId, const ConfigKey& configKey,
-                                            wp<PullDataReceiver> receiver) {
+                                            const wp<PullDataReceiver>& receiver) {
     std::lock_guard<std::mutex> _l(mLock);
     auto receiversIt = mReceivers.find({.atomTag = tagId, .configKey = configKey});
     if (receiversIt == mReceivers.end()) {
@@ -202,13 +204,13 @@
 }
 
 void StatsPullerManager::RegisterPullUidProvider(const ConfigKey& configKey,
-                                                 wp<PullUidProvider> provider) {
+                                                 const wp<PullUidProvider>& provider) {
     std::lock_guard<std::mutex> _l(mLock);
     mPullUidProviders[configKey] = provider;
 }
 
 void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey,
-                                                   wp<PullUidProvider> provider) {
+                                                   const wp<PullUidProvider>& provider) {
     std::lock_guard<std::mutex> _l(mLock);
     const auto& it = mPullUidProviders.find(configKey);
     if (it != mPullUidProviders.end() && it->second == provider) {
@@ -217,6 +219,7 @@
 }
 
 void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     int64_t wallClockNs = getWallClockNs();
 
@@ -231,8 +234,8 @@
                 // receiver to the list that will pull on this alarm.
                 // If pullNecessary is false, check if next pull time needs to be updated.
                 sp<PullDataReceiver> receiverPtr = receiverInfo.receiver.promote();
-                const bool pullNecessary = receiverPtr != nullptr && receiverPtr->isPullNeeded();
-                if (receiverInfo.nextPullTimeNs <= elapsedTimeNs && pullNecessary) {
+                if (receiverInfo.nextPullTimeNs <= elapsedTimeNs && receiverPtr != nullptr &&
+                    receiverPtr->isPullNeeded()) {
                     receivers.push_back(&receiverInfo);
                 } else {
                     if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) {
@@ -294,6 +297,7 @@
 }
 
 int StatsPullerManager::ForceClearPullerCache() {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     int totalCleared = 0;
     for (const auto& pulledAtom : kAllPullAtomInfo) {
@@ -303,6 +307,7 @@
 }
 
 int StatsPullerManager::ClearPullerCacheIfNecessary(int64_t timestampNs) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     int totalCleared = 0;
     for (const auto& pulledAtom : kAllPullAtomInfo) {
@@ -315,6 +320,7 @@
                                                   const int64_t coolDownNs, const int64_t timeoutNs,
                                                   const vector<int32_t>& additiveFields,
                                                   const shared_ptr<IPullAtomCallback>& callback) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
 
@@ -339,6 +345,7 @@
 }
 
 void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) {
+    ATRACE_CALL();
     std::lock_guard<std::mutex> _l(mLock);
     PullerKey key = {.uid = uid, .atomTag = atomTag};
     if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) {
diff --git a/statsd/src/external/StatsPullerManager.h b/statsd/src/external/StatsPullerManager.h
index 80a1331..9f5a41c 100644
--- a/statsd/src/external/StatsPullerManager.h
+++ b/statsd/src/external/StatsPullerManager.h
@@ -70,20 +70,21 @@
     // Registers a receiver for tagId. It will be pulled on the nextPullTimeNs
     // and then every intervalNs thereafter.
     virtual void RegisterReceiver(int tagId, const ConfigKey& configKey,
-                                  wp<PullDataReceiver> receiver, int64_t nextPullTimeNs,
+                                  const wp<PullDataReceiver>& receiver, int64_t nextPullTimeNs,
                                   int64_t intervalNs);
 
     // Stop listening on a tagId.
     virtual void UnRegisterReceiver(int tagId, const ConfigKey& configKey,
-                                    wp<PullDataReceiver> receiver);
+                                    const wp<PullDataReceiver>& receiver);
 
     // Registers a pull uid provider for the config key. When pulling atoms, it will be used to
     // determine which uids to pull from.
-    virtual void RegisterPullUidProvider(const ConfigKey& configKey, wp<PullUidProvider> provider);
+    virtual void RegisterPullUidProvider(const ConfigKey& configKey,
+                                         const wp<PullUidProvider>& provider);
 
     // Unregister a pull uid provider.
     virtual void UnregisterPullUidProvider(const ConfigKey& configKey,
-                                           wp<PullUidProvider> provider);
+                                           const wp<PullUidProvider>& provider);
 
     // Verify if we know how to pull for this matcher
     bool PullerForMatcherExists(int tagId) const;
@@ -101,11 +102,11 @@
     //      registered for any of the uids for this atom.
     // If the metric wants to make any change to the data, like timestamps, they
     // should make a copy as this data may be shared with multiple metrics.
-    virtual bool Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
+    virtual bool Pull(int tagId, const ConfigKey& configKey, int64_t eventTimeNs,
                       vector<std::shared_ptr<LogEvent>>* data);
 
     // Same as above, but directly specify the allowed uids to pull from.
-    virtual bool Pull(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
+    virtual bool Pull(int tagId, const vector<int32_t>& uids, int64_t eventTimeNs,
                       vector<std::shared_ptr<LogEvent>>* data);
 
     // Clear pull data cache immediately.
@@ -114,9 +115,9 @@
     // Clear pull data cache if it is beyond respective cool down time.
     int ClearPullerCacheIfNecessary(int64_t timestampNs);
 
-    void SetStatsCompanionService(shared_ptr<IStatsCompanionService> statsCompanionService);
+    void SetStatsCompanionService(const shared_ptr<IStatsCompanionService>& statsCompanionService);
 
-    void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs,
+    void RegisterPullAtomCallback(const int uid, const int32_t atomTag, int64_t coolDownNs,
                                   const int64_t timeoutNs, const vector<int32_t>& additiveFields,
                                   const shared_ptr<IPullAtomCallback>& callback);
 
@@ -151,10 +152,10 @@
     // mapping from Config Key to the PullUidProvider for that config
     std::map<ConfigKey, wp<PullUidProvider>> mPullUidProviders;
 
-    bool PullLocked(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
+    bool PullLocked(int tagId, const ConfigKey& configKey, int64_t eventTimeNs,
                     vector<std::shared_ptr<LogEvent>>* data);
 
-    bool PullLocked(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
+    bool PullLocked(int tagId, const vector<int32_t>& uids, int64_t eventTimeNs,
                     vector<std::shared_ptr<LogEvent>>* data);
 
     // locks for data receiver and StatsCompanionService changes
diff --git a/statsd/src/external/Uprobestats.cpp b/statsd/src/external/Uprobestats.cpp
new file mode 100644
index 0000000..ad6613c
--- /dev/null
+++ b/statsd/src/external/Uprobestats.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define STATSD_DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <private/android_filesystem_config.h>
+
+#include <string>
+
+#include "src/statsd_config.pb.h"  // Alert
+
+namespace android {
+namespace os {
+namespace statsd {
+
+bool StartUprobeStats(const UprobestatsDetails& config) {
+    // TODO: Add an implementation.
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/external/Uprobestats.h b/statsd/src/external/Uprobestats.h
new file mode 100644
index 0000000..e045e8f
--- /dev/null
+++ b/statsd/src/external/Uprobestats.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Starts the uprobestats process.
+bool StartUprobeStats(const UprobestatsDetails& config);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/flags/FlagProvider.h b/statsd/src/flags/FlagProvider.h
index 813dae5..7b77c04 100644
--- a/statsd/src/flags/FlagProvider.h
+++ b/statsd/src/flags/FlagProvider.h
@@ -112,9 +112,15 @@
     friend class KllMetricE2eAbTest;
     friend class MetricsManagerTest;
     friend class StatsLogProcessorTest;
+    friend class StatsLogProcessorTestRestricted;
+    friend class RestrictedEventMetricProducerTest;
+    friend class RestrictedConfigE2ETest;
+    friend class RestrictedEventMetricE2eTest;
+    friend class LogEvent_FieldRestrictionTest;
 
     FRIEND_TEST(ConfigUpdateE2eTest, TestEventMetric);
     FRIEND_TEST(ConfigUpdateE2eTest, TestGaugeMetric);
+    FRIEND_TEST(ConfigUpdateE2eTest, TestConfigUpdateRestrictedDelegateCleared);
     FRIEND_TEST(EventMetricE2eTest, TestEventMetricDataAggregated);
     FRIEND_TEST(EventMetricProducerTest, TestOneAtomTagAggregatedEvents);
     FRIEND_TEST(EventMetricProducerTest, TestTwoAtomTagAggregatedEvents);
@@ -126,6 +132,13 @@
     FRIEND_TEST(FlagProviderTest_SPlus, TestGetFlagBoolServerFlagEmptyDefaultTrue);
     FRIEND_TEST(FlagProviderTest_SPlus_RealValues, TestGetBootFlagBoolServerFlagTrue);
     FRIEND_TEST(FlagProviderTest_SPlus_RealValues, TestGetBootFlagBoolServerFlagFalse);
+    FRIEND_TEST(MetricsManagerTest_SPlus, TestRestrictedMetricsConfig);
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestFlagDisabled);
+    FRIEND_TEST(LogEventTest, TestRestrictionCategoryAnnotation);
+    FRIEND_TEST(LogEventTest, TestInvalidRestrictionCategoryAnnotation);
+    FRIEND_TEST(LogEvent_FieldRestrictionTest, TestFieldRestrictionAnnotation);
+    FRIEND_TEST(LogEvent_FieldRestrictionTest, TestInvalidAnnotationIntType);
+    FRIEND_TEST(LogEvent_FieldRestrictionTest, TestInvalidAnnotationAtomLevel);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/guardrail/StatsdStats.cpp b/statsd/src/guardrail/StatsdStats.cpp
index 626114f..2d9586f 100644
--- a/statsd/src/guardrail/StatsdStats.cpp
+++ b/statsd/src/guardrail/StatsdStats.cpp
@@ -57,8 +57,22 @@
 const int FIELD_ID_LOGGER_ERROR_STATS = 16;
 const int FIELD_ID_OVERFLOW = 18;
 const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL = 19;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS = 20;
 const int FIELD_ID_SHARD_OFFSET = 21;
+const int FIELD_ID_STATSD_STATS_ID = 22;
 const int FIELD_ID_SUBSCRIPTION_STATS = 23;
+const int FIELD_ID_SOCKET_LOSS_STATS = 24;
+const int FIELD_ID_QUEUE_STATS = 25;
+
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CALLING_UID = 1;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_ID = 2;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_UID = 3;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_PACKAGE = 4;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_INVALID_QUERY_REASON = 5;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_QUERY_WALL_TIME_NS = 6;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_HAS_ERROR = 7;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_ERROR = 8;
+const int FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_LATENCY_NS = 9;
 
 const int FIELD_ID_ATOM_STATS_TAG = 1;
 const int FIELD_ID_ATOM_STATS_COUNT = 2;
@@ -80,6 +94,9 @@
 const int FIELD_ID_OVERFLOW_MAX_HISTORY = 2;
 const int FIELD_ID_OVERFLOW_MIN_HISTORY = 3;
 
+const int FIELD_ID_QUEUE_MAX_SIZE_OBSERVED = 1;
+const int FIELD_ID_QUEUE_MAX_SIZE_OBSERVED_ELAPSED_NANOS = 2;
+
 const int FIELD_ID_CONFIG_STATS_UID = 1;
 const int FIELD_ID_CONFIG_STATS_ID = 2;
 const int FIELD_ID_CONFIG_STATS_CREATION = 3;
@@ -106,6 +123,19 @@
 const int FIELD_ID_CONFIG_STATS_DEACTIVATION = 23;
 const int FIELD_ID_CONFIG_STATS_ANNOTATION_INT64 = 1;
 const int FIELD_ID_CONFIG_STATS_ANNOTATION_INT32 = 2;
+const int FIELD_ID_CONFIG_STATS_RESTRICTED_METRIC_STATS = 25;
+const int FIELD_ID_CONFIG_STATS_DEVICE_INFO_TABLE_CREATION_FAILED = 26;
+const int FIELD_ID_CONFIG_STATS_RESTRICTED_DB_CORRUPTED_COUNT = 27;
+const int FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_FLUSH_LATENCY = 28;
+const int FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_DB_SIZE_TIME_SEC = 29;
+const int FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_DB_SIZE_BYTES = 30;
+const int FIELD_ID_CONFIG_STATS_DUMP_REPORT_NUMBER = 31;
+const int FIELD_ID_DB_DELETION_STAT_FAILED = 32;
+const int FIELD_ID_DB_DELETION_SIZE_EXCEEDED_LIMIT = 33;
+const int FIELD_ID_DB_DELETION_CONFIG_INVALID = 34;
+const int FIELD_ID_DB_DELETION_TOO_OLD = 35;
+const int FIELD_ID_DB_DELETION_CONFIG_REMOVED = 36;
+const int FIELD_ID_DB_DELETION_CONFIG_UPDATED = 37;
 
 const int FIELD_ID_INVALID_CONFIG_REASON_ENUM = 1;
 const int FIELD_ID_INVALID_CONFIG_REASON_METRIC_ID = 2;
@@ -133,6 +163,33 @@
 const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_UID = 1;
 const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_TIME = 2;
 
+// SocketLossStats
+const int FIELD_ID_SOCKET_LOSS_STATS_PER_UID = 1;
+const int FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS = 2;
+
+// for LossStatsOverflowCounters proto
+const int FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS_UID = 1;
+const int FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS_COUNT = 2;
+
+// for LossStatsPerUid proto
+const int FIELD_ID_SOCKET_LOSS_STATS_UID = 1;
+const int FIELD_ID_SOCKET_LOSS_STATS_FIRST_TIMESTAMP_NANOS = 2;
+const int FIELD_ID_SOCKET_LOSS_STATS_LAST_TIMESTAMP_NANOS = 3;
+const int FIELD_ID_SOCKET_LOSS_ATOM_ID_LOSS_STATS = 4;
+
+// for AtomIdLossStats proto
+const int FIELD_ID_ATOM_ID_LOSS_STATS_ATOM_ID = 1;
+const int FIELD_ID_ATOM_ID_LOSS_STATS_ERROR = 2;
+const int FIELD_ID_ATOM_ID_LOSS_STATS_COUNT = 3;
+
+// for RestrictedMetricStats proto
+const int FIELD_ID_RESTRICTED_STATS_METRIC_ID = 1;
+const int FIELD_ID_RESTRICTED_STATS_INSERT_ERROR = 2;
+const int FIELD_ID_RESTRICTED_STATS_TABLE_CREATION_ERROR = 3;
+const int FIELD_ID_RESTRICTED_STATS_TABLE_DELETION_ERROR = 4;
+const int FIELD_ID_RESTRICTED_STATS_FLUSH_LATENCY = 5;
+const int FIELD_ID_RESTRICTED_STATS_CATEGORY_CHANGED_COUNT = 6;
+
 const int FIELD_ID_SUBSCRIPTION_STATS_PER_SUBSCRIPTION_STATS = 1;
 const int FIELD_ID_SUBSCRIPTION_STATS_PULL_THREAD_WAKEUP_COUNT = 2;
 
@@ -149,7 +206,7 @@
         {util::CPU_TIME_PER_UID_FREQ, {6000, 10000}},
 };
 
-StatsdStats::StatsdStats() {
+StatsdStats::StatsdStats() : mStatsdStatsId(rand()) {
     mPushedAtomStats.resize(kMaxPushedAtomId + 1);
     mStartTimeSec = getWallClockSec();
 }
@@ -308,6 +365,15 @@
     noteAtomDroppedLocked(atomId);
 }
 
+void StatsdStats::noteEventQueueSize(int32_t size, int64_t eventTimestampNs) {
+    lock_guard<std::mutex> lock(mLock);
+
+    if (mEventQueueMaxSizeObserved < size) {
+        mEventQueueMaxSizeObserved = size;
+        mEventQueueMaxSizeObservedElapsedNanos = eventTimestampNs;
+    }
+}
+
 void StatsdStats::noteAtomDroppedLocked(int32_t atomId) {
     constexpr int kMaxPushedAtomDroppedStatsSize = kMaxPushedAtomId + kMaxNonPlatformPushedAtoms;
     if (mPushedAtomDropsStats.size() < kMaxPushedAtomDroppedStatsSize ||
@@ -316,6 +382,34 @@
     }
 }
 
+void StatsdStats::noteAtomSocketLoss(const SocketLossInfo& lossInfo) {
+    ALOGW("SocketLossEvent detected: %lld (firstLossTsNanos), %lld (lastLossTsNanos)",
+          (long long)lossInfo.firstLossTsNanos, (long long)lossInfo.lastLossTsNanos);
+    lock_guard<std::mutex> lock(mLock);
+
+    if (mSocketLossStats.size() == kMaxSocketLossStatsSize) {
+        // erase the oldest record
+        mSocketLossStats.pop_front();
+    }
+    mSocketLossStats.emplace_back(lossInfo.uid, lossInfo.firstLossTsNanos,
+                                  lossInfo.lastLossTsNanos);
+    for (size_t i = 0; i < lossInfo.atomIds.size(); i++) {
+        ALOGW("For uid %d atom %d was lost %d times with error %d", lossInfo.uid,
+              lossInfo.atomIds[i], lossInfo.counts[i], lossInfo.errors[i]);
+        mSocketLossStats.back().mLossCountPerErrorAtomId.emplace_back(
+                lossInfo.atomIds[i], lossInfo.errors[i], lossInfo.counts[i]);
+    }
+
+    if (lossInfo.overflowCounter > 0) {
+        auto overflowPerUid = mSocketLossStatsOverflowCounters.find(lossInfo.uid);
+        if (overflowPerUid != mSocketLossStatsOverflowCounters.end()) {
+            overflowPerUid->second += lossInfo.overflowCounter;
+        } else if (mSocketLossStatsOverflowCounters.size() < kMaxSocketLossStatsSize) {
+            mSocketLossStatsOverflowCounters[lossInfo.uid] = lossInfo.overflowCounter;
+        }
+    }
+}
+
 void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec) {
     lock_guard<std::mutex> lock(mLock);
     auto it = mConfigStats.find(key);
@@ -331,22 +425,104 @@
     it->second->data_drop_bytes.push_back(totalBytes);
 }
 
-void StatsdStats::noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes) {
-    noteMetricsReportSent(key, num_bytes, getWallClockSec());
+void StatsdStats::noteMetricsReportSent(const ConfigKey& key, const size_t numBytes,
+                                        const int32_t reportNumber) {
+    noteMetricsReportSent(key, numBytes, getWallClockSec(), reportNumber);
 }
 
-void StatsdStats::noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes,
-                                        int32_t timeSec) {
+void StatsdStats::noteMetricsReportSent(const ConfigKey& key, const size_t numBytes,
+                                        int32_t timeSec, const int32_t reportNumber) {
     lock_guard<std::mutex> lock(mLock);
     auto it = mConfigStats.find(key);
     if (it == mConfigStats.end()) {
         ALOGE("Config key %s not found!", key.ToString().c_str());
         return;
     }
+
     if (it->second->dump_report_stats.size() == kMaxTimestampCount) {
         it->second->dump_report_stats.pop_front();
     }
-    it->second->dump_report_stats.push_back(std::make_pair(timeSec, num_bytes));
+    it->second->dump_report_stats.emplace_back(timeSec, numBytes, reportNumber);
+}
+
+void StatsdStats::noteDeviceInfoTableCreationFailed(const ConfigKey& key) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    it->second->device_info_table_creation_failed = true;
+}
+
+void StatsdStats::noteDbCorrupted(const ConfigKey& key) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    it->second->db_corrupted_count++;
+}
+
+void StatsdStats::noteDbSizeExceeded(const ConfigKey& key) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    it->second->db_deletion_size_exceeded_limit++;
+}
+
+void StatsdStats::noteDbStatFailed(const ConfigKey& key) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    it->second->db_deletion_stat_failed++;
+}
+
+void StatsdStats::noteDbConfigInvalid(const ConfigKey& key) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    it->second->db_deletion_config_invalid++;
+}
+
+void StatsdStats::noteDbTooOld(const ConfigKey& key) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    it->second->db_deletion_too_old++;
+}
+
+void StatsdStats::noteDbDeletionConfigRemoved(const ConfigKey& key) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    it->second->db_deletion_config_removed++;
+}
+
+void StatsdStats::noteDbDeletionConfigUpdated(const ConfigKey& key) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(key);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", key.ToString().c_str());
+        return;
+    }
+    it->second->db_deletion_config_updated++;
 }
 
 void StatsdStats::noteUidMapDropped(int deltas) {
@@ -369,7 +545,7 @@
     mUidMapStats.bytes_used = bytes;
 }
 
-void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size) {
+void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const int64_t id, int size) {
     lock_guard<std::mutex> lock(mLock);
     // if name doesn't exist before, it will create the key with count 0.
     auto statsIt = mConfigStats.find(key);
@@ -383,7 +559,7 @@
     }
 }
 
-void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size) {
+void StatsdStats::noteMetricDimensionSize(const ConfigKey& key, const int64_t id, int size) {
     lock_guard<std::mutex> lock(mLock);
     // if name doesn't exist before, it will create the key with count 0.
     auto statsIt = mConfigStats.find(key);
@@ -396,8 +572,8 @@
     }
 }
 
-void StatsdStats::noteMetricDimensionInConditionSize(
-        const ConfigKey& key, const int64_t& id, int size) {
+void StatsdStats::noteMetricDimensionInConditionSize(const ConfigKey& key, const int64_t id,
+                                                     int size) {
     lock_guard<std::mutex> lock(mLock);
     // if name doesn't exist before, it will create the key with count 0.
     auto statsIt = mConfigStats.find(key);
@@ -410,7 +586,7 @@
     }
 }
 
-void StatsdStats::noteMatcherMatched(const ConfigKey& key, const int64_t& id) {
+void StatsdStats::noteMatcherMatched(const ConfigKey& key, const int64_t id) {
     lock_guard<std::mutex> lock(mLock);
 
     auto statsIt = mConfigStats.find(key);
@@ -420,7 +596,7 @@
     statsIt->second->matcher_stats[id]++;
 }
 
-void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const int64_t& id) {
+void StatsdStats::noteAnomalyDeclared(const ConfigKey& key, const int64_t id) {
     lock_guard<std::mutex> lock(mLock);
     auto statsIt = mConfigStats.find(key);
     if (statsIt == mConfigStats.end()) {
@@ -640,6 +816,137 @@
     return false;
 }
 
+void StatsdStats::noteQueryRestrictedMetricSucceed(const int64_t configId,
+                                                   const string& configPackage,
+                                                   const std::optional<int32_t> configUid,
+                                                   const int32_t callingUid,
+                                                   const int64_t latencyNs) {
+    lock_guard<std::mutex> lock(mLock);
+
+    if (mRestrictedMetricQueryStats.size() == kMaxRestrictedMetricQueryCount) {
+        mRestrictedMetricQueryStats.pop_front();
+    }
+    mRestrictedMetricQueryStats.emplace_back(RestrictedMetricQueryStats(
+            callingUid, configId, configPackage, configUid, getWallClockNs(),
+            /*invalidQueryReason=*/std::nullopt, /*error=*/"", latencyNs));
+}
+
+void StatsdStats::noteQueryRestrictedMetricFailed(const int64_t configId,
+                                                  const string& configPackage,
+                                                  const std::optional<int32_t> configUid,
+                                                  const int32_t callingUid,
+                                                  const InvalidQueryReason reason) {
+    lock_guard<std::mutex> lock(mLock);
+    noteQueryRestrictedMetricFailedLocked(configId, configPackage, configUid, callingUid, reason,
+                                          /*error=*/"");
+}
+
+void StatsdStats::noteQueryRestrictedMetricFailed(
+        const int64_t configId, const string& configPackage, const std::optional<int32_t> configUid,
+        const int32_t callingUid, const InvalidQueryReason reason, const string& error) {
+    lock_guard<std::mutex> lock(mLock);
+    noteQueryRestrictedMetricFailedLocked(configId, configPackage, configUid, callingUid, reason,
+                                          error);
+}
+
+void StatsdStats::noteQueryRestrictedMetricFailedLocked(
+        const int64_t configId, const string& configPackage, const std::optional<int32_t> configUid,
+        const int32_t callingUid, const InvalidQueryReason reason, const string& error) {
+    if (mRestrictedMetricQueryStats.size() == kMaxRestrictedMetricQueryCount) {
+        mRestrictedMetricQueryStats.pop_front();
+    }
+    mRestrictedMetricQueryStats.emplace_back(RestrictedMetricQueryStats(
+            callingUid, configId, configPackage, configUid, getWallClockNs(), reason, error,
+            /*queryLatencyNs=*/std::nullopt));
+}
+
+void StatsdStats::noteRestrictedMetricInsertError(const ConfigKey& configKey,
+                                                  const int64_t metricId) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(configKey);
+    if (it != mConfigStats.end()) {
+        it->second->restricted_metric_stats[metricId].insertError++;
+    }
+}
+
+void StatsdStats::noteRestrictedMetricTableCreationError(const ConfigKey& configKey,
+                                                         const int64_t metricId) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(configKey);
+    if (it != mConfigStats.end()) {
+        it->second->restricted_metric_stats[metricId].tableCreationError++;
+    }
+}
+
+void StatsdStats::noteRestrictedMetricTableDeletionError(const ConfigKey& configKey,
+                                                         const int64_t metricId) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(configKey);
+    if (it != mConfigStats.end()) {
+        it->second->restricted_metric_stats[metricId].tableDeletionError++;
+    }
+}
+
+void StatsdStats::noteRestrictedMetricFlushLatency(const ConfigKey& configKey,
+                                                   const int64_t metricId,
+                                                   const int64_t flushLatencyNs) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(configKey);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", configKey.ToString().c_str());
+        return;
+    }
+    auto& restrictedMetricStats = it->second->restricted_metric_stats[metricId];
+    if (restrictedMetricStats.flushLatencyNs.size() == kMaxRestrictedMetricFlushLatencyCount) {
+        restrictedMetricStats.flushLatencyNs.pop_front();
+    }
+    restrictedMetricStats.flushLatencyNs.push_back(flushLatencyNs);
+}
+
+void StatsdStats::noteRestrictedConfigFlushLatency(const ConfigKey& configKey,
+                                                   const int64_t totalFlushLatencyNs) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(configKey);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", configKey.ToString().c_str());
+        return;
+    }
+    std::list<int64_t>& totalFlushLatencies = it->second->total_flush_latency_ns;
+    if (totalFlushLatencies.size() == kMaxRestrictedConfigFlushLatencyCount) {
+        totalFlushLatencies.pop_front();
+    }
+    totalFlushLatencies.push_back(totalFlushLatencyNs);
+}
+
+void StatsdStats::noteRestrictedConfigDbSize(const ConfigKey& configKey,
+                                             const int64_t elapsedTimeNs, const int64_t dbSize) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(configKey);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", configKey.ToString().c_str());
+        return;
+    }
+    std::list<int64_t>& totalDbSizeTimestamps = it->second->total_db_size_timestamps;
+    std::list<int64_t>& totaDbSizes = it->second->total_db_sizes;
+    if (totalDbSizeTimestamps.size() == kMaxRestrictedConfigDbSizeCount) {
+        totalDbSizeTimestamps.pop_front();
+        totaDbSizes.pop_front();
+    }
+    totalDbSizeTimestamps.push_back(elapsedTimeNs);
+    totaDbSizes.push_back(dbSize);
+}
+
+void StatsdStats::noteRestrictedMetricCategoryChanged(const ConfigKey& configKey,
+                                                      const int64_t metricId) {
+    lock_guard<std::mutex> lock(mLock);
+    auto it = mConfigStats.find(configKey);
+    if (it == mConfigStats.end()) {
+        ALOGE("Config key %s not found!", configKey.ToString().c_str());
+        return;
+    }
+    it->second->restricted_metric_stats[metricId].categoryChangedCount++;
+}
+
 void StatsdStats::noteSubscriptionStarted(int subId, int32_t pushedAtomCount,
                                           int32_t pulledAtomCount) {
     lock_guard<std::mutex> lock(mLock);
@@ -735,6 +1042,8 @@
     mOverflowCount = 0;
     mMinQueueHistoryNs = kInt64Max;
     mMaxQueueHistoryNs = 0;
+    mEventQueueMaxSizeObserved = 0;
+    mEventQueueMaxSizeObservedElapsedNanos = 0;
     for (auto& config : mConfigStats) {
         config.second->broadcast_sent_time_sec.clear();
         config.second->activation_time_sec.clear();
@@ -748,6 +1057,17 @@
         config.second->metric_stats.clear();
         config.second->metric_dimension_in_condition_stats.clear();
         config.second->alert_stats.clear();
+        config.second->restricted_metric_stats.clear();
+        config.second->db_corrupted_count = 0;
+        config.second->total_flush_latency_ns.clear();
+        config.second->total_db_size_timestamps.clear();
+        config.second->total_db_sizes.clear();
+        config.second->db_deletion_size_exceeded_limit = 0;
+        config.second->db_deletion_stat_failed = 0;
+        config.second->db_deletion_config_invalid = 0;
+        config.second->db_deletion_too_old = 0;
+        config.second->db_deletion_config_removed = 0;
+        config.second->db_deletion_config_updated = 0;
     }
     for (auto& pullStats : mPulledAtomStats) {
         pullStats.second.totalPull = 0;
@@ -775,7 +1095,10 @@
     mAtomMetricStats.clear();
     mActivationBroadcastGuardrailStats.clear();
     mPushedAtomErrorStats.clear();
+    mSocketLossStats.clear();
+    mSocketLossStatsOverflowCounters.clear();
     mPushedAtomDropsStats.clear();
+    mRestrictedMetricQueryStats.clear();
     mSubscriptionPullThreadWakeupCount = 0;
 
     for (auto it = mSubscriptionStats.begin(); it != mSubscriptionStats.end();) {
@@ -816,6 +1139,13 @@
     }
 }
 
+bool StatsdStats::hasRestrictedConfigErrors(const std::shared_ptr<ConfigStats>& configStats) const {
+    return configStats->device_info_table_creation_failed || configStats->db_corrupted_count ||
+           configStats->db_deletion_size_exceeded_limit || configStats->db_deletion_stat_failed ||
+           configStats->db_deletion_config_invalid || configStats->db_deletion_too_old ||
+           configStats->db_deletion_config_removed || configStats->db_deletion_config_updated;
+}
+
 bool StatsdStats::hasEventQueueOverflow() const {
     lock_guard<std::mutex> lock(mLock);
     return mOverflowCount != 0;
@@ -837,12 +1167,24 @@
     for (const auto& configStats : mIceBox) {
         dprintf(out,
                 "Config {%d_%lld}: creation=%d, deletion=%d, reset=%d, #metric=%d, #condition=%d, "
-                "#matcher=%d, #alert=%d, valid=%d\n",
+                "#matcher=%d, #alert=%d, valid=%d",
                 configStats->uid, (long long)configStats->id, configStats->creation_time_sec,
                 configStats->deletion_time_sec, configStats->reset_time_sec,
                 configStats->metric_count, configStats->condition_count, configStats->matcher_count,
                 configStats->alert_count, configStats->is_valid);
-
+        if (hasRestrictedConfigErrors(configStats)) {
+            dprintf(out,
+                    ", device_info_table_creation_failed=%d, db_corrupted_count=%d, "
+                    "db_size_exceeded=%d, db_stat_failed=%d, "
+                    "db_config_invalid=%d, db_too_old=%d, db_deletion_config_removed=%d, "
+                    "db_deletion_config_updated=%d",
+                    configStats->device_info_table_creation_failed, configStats->db_corrupted_count,
+                    configStats->db_deletion_size_exceeded_limit,
+                    configStats->db_deletion_stat_failed, configStats->db_deletion_config_invalid,
+                    configStats->db_deletion_too_old, configStats->db_deletion_config_removed,
+                    configStats->db_deletion_config_updated);
+        }
+        dprintf(out, "\n");
         if (!configStats->is_valid) {
             dprintf(out, "\tinvalid config reason: %s\n",
                     InvalidConfigReasonEnum_Name(configStats->reason->reason).c_str());
@@ -868,18 +1210,54 @@
                     buildTimeString(*dropTimePtr).c_str(), (long long)*dropTimePtr,
                     (long long)*dropBytesPtr);
         }
+
+        for (const auto& stats : configStats->restricted_metric_stats) {
+            dprintf(out, "Restricted MetricId %lld: ", (long long)stats.first);
+            dprintf(out, "Insert error %lld, ", (long long)stats.second.insertError);
+            dprintf(out, "Table creation error %lld, ", (long long)stats.second.tableCreationError);
+            dprintf(out, "Table deletion error %lld ", (long long)stats.second.tableDeletionError);
+            dprintf(out, "Category changed count %lld\n ",
+                    (long long)stats.second.categoryChangedCount);
+            string flushLatencies = "Flush Latencies: ";
+            for (const int64_t latencyNs : stats.second.flushLatencyNs) {
+                flushLatencies.append(to_string(latencyNs).append(","));
+            }
+            flushLatencies.pop_back();
+            flushLatencies.push_back('\n');
+            dprintf(out, "%s", flushLatencies.c_str());
+        }
+
+        for (const int64_t flushLatency : configStats->total_flush_latency_ns) {
+            dprintf(out, "\tflush latency time ns: %lld\n", (long long)flushLatency);
+        }
+
+        for (const int64_t dbSize : configStats->total_db_sizes) {
+            dprintf(out, "\tdb size: %lld\n", (long long)dbSize);
+        }
     }
     dprintf(out, "%lu Active Configs\n", (unsigned long)mConfigStats.size());
     for (auto& pair : mConfigStats) {
         auto& configStats = pair.second;
         dprintf(out,
                 "Config {%d-%lld}: creation=%d, deletion=%d, #metric=%d, #condition=%d, "
-                "#matcher=%d, #alert=%d, valid=%d\n",
+                "#matcher=%d, #alert=%d, valid=%d",
                 configStats->uid, (long long)configStats->id, configStats->creation_time_sec,
                 configStats->deletion_time_sec, configStats->metric_count,
                 configStats->condition_count, configStats->matcher_count, configStats->alert_count,
                 configStats->is_valid);
-
+        if (hasRestrictedConfigErrors(configStats)) {
+            dprintf(out,
+                    ", device_info_table_creation_failed=%d, db_corrupted_count=%d, "
+                    "db_size_exceeded=%d, db_stat_failed=%d, "
+                    "db_config_invalid=%d, db_too_old=%d, db_deletion_config_removed=%d, "
+                    "db_deletion_config_updated=%d",
+                    configStats->device_info_table_creation_failed, configStats->db_corrupted_count,
+                    configStats->db_deletion_size_exceeded_limit,
+                    configStats->db_deletion_stat_failed, configStats->db_deletion_config_invalid,
+                    configStats->db_deletion_too_old, configStats->db_deletion_config_removed,
+                    configStats->db_deletion_config_updated);
+        }
+        dprintf(out, "\n");
         if (!configStats->is_valid) {
             dprintf(out, "\tinvalid config reason: %s\n",
                     InvalidConfigReasonEnum_Name(configStats->reason->reason).c_str());
@@ -913,9 +1291,10 @@
         }
 
         for (const auto& dump : configStats->dump_report_stats) {
-            dprintf(out, "\tdump report time: %s(%lld) bytes: %lld\n",
-                    buildTimeString(dump.first).c_str(), (long long)dump.first,
-                    (long long)dump.second);
+            dprintf(out, "\tdump report time: %s(%lld) bytes: %d reportNumber: %d\n",
+                    buildTimeString(dump.mDumpReportTimeSec).c_str(),
+                    (long long)dump.mDumpReportTimeSec, dump.mDumpReportSizeBytes,
+                    dump.mDumpReportNumber);
         }
 
         for (const auto& stats : pair.second->matcher_stats) {
@@ -935,6 +1314,30 @@
         for (const auto& stats : pair.second->alert_stats) {
             dprintf(out, "alert %lld declared %d times\n", (long long)stats.first, stats.second);
         }
+
+        for (const auto& stats : configStats->restricted_metric_stats) {
+            dprintf(out, "Restricted MetricId %lld: ", (long long)stats.first);
+            dprintf(out, "Insert error %lld, ", (long long)stats.second.insertError);
+            dprintf(out, "Table creation error %lld, ", (long long)stats.second.tableCreationError);
+            dprintf(out, "Table deletion error %lld ", (long long)stats.second.tableDeletionError);
+            dprintf(out, "Category changed count %lld\n ",
+                    (long long)stats.second.categoryChangedCount);
+            string flushLatencies = "Flush Latencies: ";
+            for (const int64_t latencyNs : stats.second.flushLatencyNs) {
+                flushLatencies.append(to_string(latencyNs).append(","));
+            }
+            flushLatencies.pop_back();
+            flushLatencies.push_back('\n');
+            dprintf(out, "%s", flushLatencies.c_str());
+        }
+
+        for (const int64_t flushLatency : configStats->total_flush_latency_ns) {
+            dprintf(out, "flush latency time ns: %lld\n", (long long)flushLatency);
+        }
+
+        for (const int64_t dbSize : configStats->total_db_sizes) {
+            dprintf(out, "\tdb size: %lld\n", (long long)dbSize);
+        }
     }
     dprintf(out, "********Disk Usage stats***********\n");
     StorageManager::printStats(out);
@@ -965,7 +1368,7 @@
                 "  (pull timeout)%ld, (pull exceed max delay)%ld"
                 "  (no uid provider count)%ld, (no puller found count)%ld\n"
                 "  (registered count) %ld, (unregistered count) %ld"
-                "  (atom error count) %d, (subscription pull count) %d\n",
+                "  (atom error count) %d, (subscription pull count) %d, (binder call failed) %ld\n",
                 (int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache,
                 (long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec,
                 (long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs,
@@ -973,7 +1376,8 @@
                 pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay,
                 pair.second.pullUidProviderNotFound, pair.second.pullerNotFound,
                 pair.second.registeredCount, pair.second.unregisteredCount,
-                pair.second.atomErrorCount, pair.second.subscriptionPullCount);
+                pair.second.atomErrorCount, pair.second.subscriptionPullCount,
+                pair.second.binderCallFailCount);
         if (pair.second.pullTimeoutMetadata.size() > 0) {
             string uptimeMillis = "(pull timeout system uptime millis) ";
             string pullTimeoutMillis = "(pull timeout elapsed time millis) ";
@@ -1017,8 +1421,31 @@
                 loss.mUid, loss.mPid);
     }
 
+    if (mSocketLossStats.size()) {
+        dprintf(out, "********SocketLossStats stats***********\n");
+        for (const auto& loss : mSocketLossStats) {
+            dprintf(out, "Socket loss: %d (uid), first loss at %lld, last loss at %lld\n",
+                    loss.mUid, (long long)loss.mFirstLossTsNanos, (long long)loss.mLastLossTsNanos);
+            for (const auto& counterInfo : loss.mLossCountPerErrorAtomId) {
+                dprintf(out, "\t\t %d (atomId) %d (error), %d (count)\n", counterInfo.mAtomId,
+                        counterInfo.mError, counterInfo.mCount);
+            }
+        }
+    }
+
+    if (mSocketLossStatsOverflowCounters.size()) {
+        dprintf(out, "********mSocketLossStatsOverflowCounters stats***********\n");
+        for (const auto& overflow : mSocketLossStatsOverflowCounters) {
+            dprintf(out, "Socket loss overflow for %d uid is %d times\n", overflow.first,
+                    overflow.second);
+        }
+    }
+
+    dprintf(out, "********EventQueueOverflow stats***********\n");
     dprintf(out, "Event queue overflow: %d; MaxHistoryNs: %lld; MinHistoryNs: %lld\n",
             mOverflowCount, (long long)mMaxQueueHistoryNs, (long long)mMinQueueHistoryNs);
+    dprintf(out, "Event queue max size: %d; Observed at : %lld\n", mEventQueueMaxSizeObserved,
+            (long long)mEventQueueMaxSizeObservedElapsedNanos);
 
     if (mActivationBroadcastGuardrailStats.size() > 0) {
         dprintf(out, "********mActivationBroadcastGuardrail stats***********\n");
@@ -1040,6 +1467,29 @@
         dprintf(out, "\n");
     }
 
+    if (mRestrictedMetricQueryStats.size() > 0) {
+        dprintf(out, "********Restricted Metric Query stats***********\n");
+        for (const auto& stat : mRestrictedMetricQueryStats) {
+            if (stat.mHasError) {
+                dprintf(out,
+                        "Query with error type: %d - %lld (query time ns), "
+                        "%d (calling uid), %lld (config id), %s (config package), %s (error)\n",
+                        stat.mInvalidQueryReason.value(), (long long)stat.mQueryWallTimeNs,
+                        stat.mCallingUid, (long long)stat.mConfigId, stat.mConfigPackage.c_str(),
+                        stat.mError.c_str());
+            } else {
+                dprintf(out,
+                        "Query succeed - %lld (query time ns), %d (calling uid), "
+                        "%lld (config id), %s (config package), %d (config uid), "
+                        "%lld (queryLatencyNs)\n",
+                        (long long)stat.mQueryWallTimeNs, stat.mCallingUid,
+                        (long long)stat.mConfigId, stat.mConfigPackage.c_str(),
+                        stat.mConfigUid.value(), (long long)stat.mQueryLatencyNs.value());
+            }
+        }
+    }
+    dprintf(out, "********Statsd Stats Id***********\n");
+    dprintf(out, "Statsd Stats Id %d\n", mStatsdStatsId);
     dprintf(out, "********Shard Offset Provider stats***********\n");
     dprintf(out, "Shard Offset: %u\n", ShardOffsetProvider::getInstance().getShardOffset());
 }
@@ -1129,15 +1579,21 @@
     }
 
     for (const auto& dump : configStats.dump_report_stats) {
-        proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DUMP_REPORT_TIME |
-                     FIELD_COUNT_REPEATED,
-                     dump.first);
+        proto->write(
+                FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DUMP_REPORT_TIME | FIELD_COUNT_REPEATED,
+                dump.mDumpReportTimeSec);
     }
 
     for (const auto& dump : configStats.dump_report_stats) {
-        proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_DUMP_REPORT_BYTES |
-                     FIELD_COUNT_REPEATED,
-                     (long long)dump.second);
+        proto->write(
+                FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DUMP_REPORT_BYTES | FIELD_COUNT_REPEATED,
+                (long long)dump.mDumpReportSizeBytes);
+    }
+
+    for (const auto& dump : configStats.dump_report_stats) {
+        proto->write(
+                FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DUMP_REPORT_NUMBER | FIELD_COUNT_REPEATED,
+                dump.mDumpReportNumber);
     }
 
     for (const auto& annotation : configStats.annotations) {
@@ -1188,6 +1644,60 @@
         proto->end(tmpToken);
     }
 
+    for (const auto& pair : configStats.restricted_metric_stats) {
+        uint64_t token =
+                proto->start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_STATS_RESTRICTED_METRIC_STATS |
+                             FIELD_COUNT_REPEATED);
+
+        proto->write(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_METRIC_ID, (long long)pair.first);
+        writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_INSERT_ERROR,
+                                 (long long)pair.second.insertError, proto);
+        writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_TABLE_CREATION_ERROR,
+                                 (long long)pair.second.tableCreationError, proto);
+        writeNonZeroStatToStream(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_TABLE_DELETION_ERROR,
+                                 (long long)pair.second.tableDeletionError, proto);
+        for (const int64_t flushLatencyNs : pair.second.flushLatencyNs) {
+            proto->write(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_FLUSH_LATENCY |
+                                 FIELD_COUNT_REPEATED,
+                         flushLatencyNs);
+        }
+        writeNonZeroStatToStream(
+                FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_STATS_CATEGORY_CHANGED_COUNT,
+                (long long)pair.second.categoryChangedCount, proto);
+        proto->end(token);
+    }
+    writeNonZeroStatToStream(
+            FIELD_TYPE_BOOL | FIELD_ID_CONFIG_STATS_DEVICE_INFO_TABLE_CREATION_FAILED,
+            configStats.device_info_table_creation_failed, proto);
+    writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_RESTRICTED_DB_CORRUPTED_COUNT,
+                             configStats.db_corrupted_count, proto);
+    writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_DB_DELETION_STAT_FAILED,
+                             configStats.db_deletion_size_exceeded_limit, proto);
+    writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_DB_DELETION_SIZE_EXCEEDED_LIMIT,
+                             configStats.db_deletion_size_exceeded_limit, proto);
+    writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_DB_DELETION_CONFIG_INVALID,
+                             configStats.db_deletion_config_invalid, proto);
+    writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_DB_DELETION_TOO_OLD,
+                             configStats.db_deletion_too_old, proto);
+    writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_DB_DELETION_CONFIG_REMOVED,
+                             configStats.db_deletion_config_removed, proto);
+    writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_DB_DELETION_CONFIG_UPDATED,
+                             configStats.db_deletion_config_updated, proto);
+    for (int64_t latency : configStats.total_flush_latency_ns) {
+        proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_FLUSH_LATENCY |
+                             FIELD_COUNT_REPEATED,
+                     latency);
+    }
+    for (int64_t dbSizeTimestamp : configStats.total_db_size_timestamps) {
+        proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_DB_SIZE_TIME_SEC |
+                             FIELD_COUNT_REPEATED,
+                     dbSizeTimestamp);
+    }
+    for (int64_t dbSize : configStats.total_db_sizes) {
+        proto->write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_RESTRICTED_CONFIG_DB_SIZE_BYTES |
+                             FIELD_COUNT_REPEATED,
+                     dbSize);
+    }
     proto->end(token);
 }
 
@@ -1291,6 +1801,13 @@
         proto.end(token);
     }
 
+    uint64_t queueStatsToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_QUEUE_STATS);
+    proto.write(FIELD_TYPE_INT32 | FIELD_ID_QUEUE_MAX_SIZE_OBSERVED,
+                (int32_t)mEventQueueMaxSizeObserved);
+    proto.write(FIELD_TYPE_INT64 | FIELD_ID_QUEUE_MAX_SIZE_OBSERVED_ELAPSED_NANOS,
+                (long long)mEventQueueMaxSizeObservedElapsedNanos);
+    proto.end(queueStatsToken);
+
     for (const auto& restart : mSystemServerRestartSec) {
         proto.write(FIELD_TYPE_INT32 | FIELD_ID_SYSTEM_SERVER_RESTART | FIELD_COUNT_REPEATED,
                     restart);
@@ -1310,9 +1827,44 @@
         proto.end(token);
     }
 
+    for (const auto& stat : mRestrictedMetricQueryStats) {
+        uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS |
+                                     FIELD_COUNT_REPEATED);
+        proto.write(FIELD_TYPE_INT32 | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CALLING_UID,
+                    stat.mCallingUid);
+        proto.write(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_ID,
+                    stat.mConfigId);
+        proto.write(FIELD_TYPE_STRING | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_PACKAGE,
+                    stat.mConfigPackage);
+        if (stat.mConfigUid.has_value()) {
+            proto.write(FIELD_TYPE_INT32 | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_CONFIG_UID,
+                        stat.mConfigUid.value());
+        }
+        if (stat.mInvalidQueryReason.has_value()) {
+            proto.write(
+                    FIELD_TYPE_ENUM | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_INVALID_QUERY_REASON,
+                    stat.mInvalidQueryReason.value());
+        }
+        proto.write(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_QUERY_WALL_TIME_NS,
+                    stat.mQueryWallTimeNs);
+        proto.write(FIELD_TYPE_BOOL | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_HAS_ERROR,
+                    stat.mHasError);
+        if (stat.mHasError && !stat.mError.empty()) {
+            proto.write(FIELD_TYPE_STRING | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_ERROR,
+                        stat.mError);
+        }
+        if (stat.mQueryLatencyNs.has_value()) {
+            proto.write(FIELD_TYPE_INT64 | FIELD_ID_RESTRICTED_METRIC_QUERY_STATS_LATENCY_NS,
+                        stat.mQueryLatencyNs.value());
+        }
+        proto.end(token);
+    }
+
     proto.write(FIELD_TYPE_UINT32 | FIELD_ID_SHARD_OFFSET,
                 static_cast<long>(ShardOffsetProvider::getInstance().getShardOffset()));
 
+    proto.write(FIELD_TYPE_INT32 | FIELD_ID_STATSD_STATS_ID, mStatsdStatsId);
+
     // Write subscription stats
     const uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SUBSCRIPTION_STATS);
     for (const auto& [id, subStats] : mSubscriptionStats) {
@@ -1338,6 +1890,47 @@
             mSubscriptionPullThreadWakeupCount, &proto);
     proto.end(token);
 
+    // libstatssocket specific stats
+
+    const uint64_t socketLossStatsToken =
+            proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SOCKET_LOSS_STATS);
+
+    // socket loss stats info per uid/error/atom id counter
+    for (const auto& perUidLossInfo : mSocketLossStats) {
+        uint64_t token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SOCKET_LOSS_STATS_PER_UID |
+                                     FIELD_COUNT_REPEATED);
+        proto.write(FIELD_TYPE_INT32 | FIELD_ID_SOCKET_LOSS_STATS_UID, perUidLossInfo.mUid);
+        proto.write(FIELD_TYPE_INT32 | FIELD_ID_SOCKET_LOSS_STATS_FIRST_TIMESTAMP_NANOS,
+                    perUidLossInfo.mFirstLossTsNanos);
+        proto.write(FIELD_TYPE_INT32 | FIELD_ID_SOCKET_LOSS_STATS_LAST_TIMESTAMP_NANOS,
+                    perUidLossInfo.mLastLossTsNanos);
+        for (const auto& counterInfo : perUidLossInfo.mLossCountPerErrorAtomId) {
+            uint64_t token =
+                    proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SOCKET_LOSS_ATOM_ID_LOSS_STATS |
+                                FIELD_COUNT_REPEATED);
+            proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ID_LOSS_STATS_ATOM_ID,
+                        counterInfo.mAtomId);
+            proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ID_LOSS_STATS_ERROR, counterInfo.mError);
+            proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ID_LOSS_STATS_COUNT, counterInfo.mCount);
+            proto.end(token);
+        }
+        proto.end(token);
+    }
+
+    // socket loss stats overflow counters
+    for (const auto& overflowInfo : mSocketLossStatsOverflowCounters) {
+        uint64_t token =
+                proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS |
+                            FIELD_COUNT_REPEATED);
+        proto.write(FIELD_TYPE_INT32 | FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS_UID,
+                    overflowInfo.first);
+        proto.write(FIELD_TYPE_INT32 | FIELD_ID_SOCKET_LOSS_STATS_OVERFLOW_COUNTERS_COUNT,
+                    overflowInfo.second);
+        proto.end(token);
+    }
+
+    proto.end(socketLossStatsToken);
+
     output->clear();
     proto.serializeToVector(output);
 
@@ -1348,11 +1941,11 @@
     VLOG("reset=%d, returned proto size %lu", reset, (unsigned long)output->size());
 }
 
-std::pair<size_t, size_t> StatsdStats::getAtomDimensionKeySizeLimits(const int atomId) {
+std::pair<size_t, size_t> StatsdStats::getAtomDimensionKeySizeLimits(int atomId,
+                                                                     size_t defaultHardLimit) {
     return kAtomDimensionKeySizeLimitMap.find(atomId) != kAtomDimensionKeySizeLimitMap.end()
                    ? kAtomDimensionKeySizeLimitMap.at(atomId)
-                   : std::make_pair<size_t, size_t>(kDimensionKeySizeSoftLimit,
-                                                    kDimensionKeySizeHardLimit);
+                   : std::pair<size_t, size_t>(kDimensionKeySizeSoftLimit, defaultHardLimit);
 }
 
 InvalidConfigReason createInvalidConfigReasonWithMatcher(const InvalidConfigReasonEnum reason,
diff --git a/statsd/src/guardrail/StatsdStats.h b/statsd/src/guardrail/StatsdStats.h
index 9e7d9e3..3adb668 100644
--- a/statsd/src/guardrail/StatsdStats.h
+++ b/statsd/src/guardrail/StatsdStats.h
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include "config/ConfigKey.h"
+#include "logd/logevent_util.h"
 
 namespace android {
 namespace os {
@@ -52,6 +53,25 @@
     }
 };
 
+typedef struct {
+    int64_t insertError = 0;
+    int64_t tableCreationError = 0;
+    int64_t tableDeletionError = 0;
+    std::list<int64_t> flushLatencyNs;
+    int64_t categoryChangedCount = 0;
+} RestrictedMetricStats;
+
+struct DumpReportStats {
+    DumpReportStats(int32_t dumpReportSec, int32_t dumpReportSize, int32_t reportNumber)
+        : mDumpReportTimeSec(dumpReportSec),
+          mDumpReportSizeBytes(dumpReportSize),
+          mDumpReportNumber(reportNumber) {
+    }
+    int32_t mDumpReportTimeSec = 0;
+    int32_t mDumpReportSizeBytes = 0;
+    int32_t mDumpReportNumber = 0;
+};
+
 struct ConfigStats {
     int32_t uid;
     int64_t id;
@@ -63,6 +83,14 @@
     int32_t matcher_count;
     int32_t alert_count;
     bool is_valid;
+    bool device_info_table_creation_failed = false;
+    int32_t db_corrupted_count = 0;
+    int32_t db_deletion_stat_failed = 0;
+    int32_t db_deletion_size_exceeded_limit = 0;
+    int32_t db_deletion_config_invalid = 0;
+    int32_t db_deletion_too_old = 0;
+    int32_t db_deletion_config_removed = 0;
+    int32_t db_deletion_config_updated = 0;
 
     // Stores reasons for why config is valid or not
     std::optional<InvalidConfigReason> reason;
@@ -78,7 +106,8 @@
     std::list<int32_t> data_drop_time_sec;
     // Number of bytes dropped at corresponding time.
     std::list<int64_t> data_drop_bytes;
-    std::list<std::pair<int32_t, int64_t>> dump_report_stats;
+
+    std::list<DumpReportStats> dump_report_stats;
 
     // Stores how many times a matcher have been matched. The map size is capped by kMaxConfigCount.
     std::map<const int64_t, int> matcher_stats;
@@ -105,6 +134,17 @@
 
     // Stores the config ID for each sub-config used.
     std::list<std::pair<const int64_t, const int32_t>> annotations;
+
+    // Maps metric ID of restricted metric to its stats.
+    std::map<int64_t, RestrictedMetricStats> restricted_metric_stats;
+
+    std::list<int64_t> total_flush_latency_ns;
+
+    // Stores the last 20 timestamps for computing sqlite db size.
+    std::list<int64_t> total_db_size_timestamps;
+
+    // Stores the last 20 sizes of the sqlite db.
+    std::list<int64_t> total_db_sizes;
 };
 
 struct UidMapStats {
@@ -131,6 +171,8 @@
 
     const static int kDimensionKeySizeSoftLimit = 500;
     static constexpr int kDimensionKeySizeHardLimit = 800;
+    static constexpr int kDimensionKeySizeHardLimitMin = 800;
+    static constexpr int kDimensionKeySizeHardLimitMax = 3000;
 
     // Per atom dimension key size limit
     static const std::map<int, std::pair<size_t, size_t>> kAtomDimensionKeySizeLimitMap;
@@ -138,8 +180,8 @@
     const static int kMaxConfigCountPerUid = 20;
     const static int kMaxAlertCountPerConfig = 200;
     const static int kMaxConditionCountPerConfig = 500;
-    const static int kMaxMetricCountPerConfig = 2000;
-    const static int kMaxMatcherCountPerConfig = 2500;
+    const static int kMaxMetricCountPerConfig = 3000;
+    const static int kMaxMatcherCountPerConfig = 3500;
 
     // The max number of old config stats we keep.
     const static int kMaxIceBoxSize = 20;
@@ -154,6 +196,14 @@
 
     const static int kMaxPullAtomPackages = 100;
 
+    const static int kMaxRestrictedMetricQueryCount = 20;
+
+    const static int kMaxRestrictedMetricFlushLatencyCount = 20;
+
+    const static int kMaxRestrictedConfigFlushLatencyCount = 20;
+
+    const static int kMaxRestrictedConfigDbSizeCount = 20;
+
     // Max memory allowed for storing metrics per configuration. If this limit is exceeded, statsd
     // drops the metrics data in memory.
     static const size_t kDefaultMaxMetricsBytesPerConfig = 2 * 1024 * 1024;
@@ -161,9 +211,17 @@
     // Hard limit for custom memory allowed for storing metrics per configuration.
     static const size_t kHardMaxMetricsBytesPerConfig = 20 * 1024 * 1024;
 
+    // Max memory allowed for storing metrics per configuration before triggering a intent to fetch
+    // data.
+    static const size_t kHardMaxTriggerGetDataBytes = 10 * 1024 * 1024;
+
     // Soft memory limit per configuration. Once this limit is exceeded, we begin notifying the
     // data subscriber that it's time to call getData.
-    static const size_t kBytesPerConfigTriggerGetData = 192 * 1024;
+    static const size_t kDefaultBytesPerConfigTriggerGetData = 192 * 1024;
+
+    // Soft memory limit per restricted configuration. Once this limit is exceeded,
+    // we begin flush in-memory restricted metrics to database.
+    static const size_t kBytesPerRestrictedConfigTriggerFlush = 25 * 1024;
 
     // Cap the UID map's memory usage to this. This should be fairly high since the UID information
     // is critical for understanding the metrics.
@@ -178,6 +236,15 @@
     /* Min period between two checks of byte size per config key in nanoseconds. */
     static const int64_t kMinByteSizeCheckPeriodNs = 60 * NS_PER_SEC;
 
+    /* Min period between two checks of restricted metrics TTLs. */
+    static const int64_t kMinTtlCheckPeriodNs = 60 * 60 * NS_PER_SEC;
+
+    /* Min period between two flush operations of restricted metrics. */
+    static const int64_t kMinFlushRestrictedPeriodNs = 60 * 60 * NS_PER_SEC;
+
+    /* Min period between two db guardrail check operations of restricted metrics. */
+    static const int64_t kMinDbGuardrailEnforcementPeriodNs = 60 * 60 * NS_PER_SEC;
+
     /* Minimum period between two activation broadcasts in nanoseconds. */
     static const int64_t kMinActivationBroadcastPeriodNs = 10 * NS_PER_SEC;
 
@@ -205,9 +272,12 @@
     // Maximum number of pushed atoms error statsd stats will track.
     static const int kMaxPushedAtomErrorStatsSize = 100;
 
+    // Maximum number of socket loss stats to track.
+    static const int kMaxSocketLossStatsSize = 50;
+
     // Maximum atom id value that we consider a platform pushed atom.
     // This should be updated once highest pushed atom id in atoms.proto approaches this value.
-    static const int kMaxPushedAtomId = 900;
+    static const int kMaxPushedAtomId = 1500;
 
     // Atom id that is the start of the pulled atoms.
     static const int kPullAtomStartTag = 10000;
@@ -272,7 +342,48 @@
      *
      * The report may be requested via StatsManager API, or through adb cmd.
      */
-    void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes);
+    void noteMetricsReportSent(const ConfigKey& key, const size_t numBytes,
+                               const int32_t reportNumber);
+
+    /**
+     * Report failure in creating the device info metadata table for restricted configs.
+     */
+    void noteDeviceInfoTableCreationFailed(const ConfigKey& key);
+
+    /**
+     * Report db corruption for restricted configs.
+     */
+    void noteDbCorrupted(const ConfigKey& key);
+
+    /**
+     * Report db exceeded the size limit for restricted configs.
+     */
+    void noteDbSizeExceeded(const ConfigKey& key);
+
+    /**
+     * Report db size check with stat for restricted configs failed.
+     */
+    void noteDbStatFailed(const ConfigKey& key);
+
+    /**
+     * Report restricted config is invalid.
+     */
+    void noteDbConfigInvalid(const ConfigKey& key);
+
+    /**
+     * Report db is too old for restricted configs.
+     */
+    void noteDbTooOld(const ConfigKey& key);
+
+    /**
+     * Report db was deleted due to config removal.
+     */
+    void noteDbDeletionConfigRemoved(const ConfigKey& key);
+
+    /**
+     * Report db was deleted due to config update.
+     */
+    void noteDbDeletionConfigUpdated(const ConfigKey& key);
 
     /**
      * Report the size of output tuple of a condition.
@@ -284,7 +395,7 @@
      * [id]: The id of the condition.
      * [size]: The output tuple size.
      */
-    void noteConditionDimensionSize(const ConfigKey& key, const int64_t& id, int size);
+    void noteConditionDimensionSize(const ConfigKey& key, int64_t id, int size);
 
     /**
      * Report the size of output tuple of a metric.
@@ -296,7 +407,7 @@
      * [id]: The id of the metric.
      * [size]: The output tuple size.
      */
-    void noteMetricDimensionSize(const ConfigKey& key, const int64_t& id, int size);
+    void noteMetricDimensionSize(const ConfigKey& key, int64_t id, int size);
 
     /**
      * Report the max size of output tuple of dimension in condition across dimensions in what.
@@ -308,7 +419,7 @@
      * [id]: The id of the metric.
      * [size]: The output tuple size.
      */
-    void noteMetricDimensionInConditionSize(const ConfigKey& key, const int64_t& id, int size);
+    void noteMetricDimensionInConditionSize(const ConfigKey& key, int64_t id, int size);
 
     /**
      * Report a matcher has been matched.
@@ -316,7 +427,7 @@
      * [key]: The config key that this matcher belongs to.
      * [id]: The id of the matcher.
      */
-    void noteMatcherMatched(const ConfigKey& key, const int64_t& id);
+    void noteMatcherMatched(const ConfigKey& key, int64_t id);
 
     /**
      * Report that an anomaly detection alert has been declared.
@@ -324,7 +435,7 @@
      * [key]: The config key that this alert belongs to.
      * [id]: The id of the alert.
      */
-    void noteAnomalyDeclared(const ConfigKey& key, const int64_t& id);
+    void noteAnomalyDeclared(const ConfigKey& key, int64_t id);
 
     /**
      * Report an atom event has been logged.
@@ -503,6 +614,9 @@
      * in the queue */
     void noteEventQueueOverflow(int64_t oldestEventTimestampNs, int32_t atomId, bool isSkipped);
 
+    /* Notes queue max size seen so far and associated timestamp */
+    void noteEventQueueSize(int32_t size, int64_t eventTimestampNs);
+
     /**
      * Reports that the activation broadcast guardrail was hit for this uid. Namely, the broadcast
      * should have been sent, but instead was skipped due to hitting the guardrail.
@@ -519,6 +633,51 @@
      */
     void noteAtomError(int atomTag, bool pull = false);
 
+    /** Report query of restricted metric succeed **/
+    void noteQueryRestrictedMetricSucceed(const int64_t configId, const string& configPackage,
+                                          const std::optional<int32_t> configUid,
+                                          const int32_t callingUid, int64_t queryLatencyNs);
+
+    /** Report query of restricted metric failed **/
+    void noteQueryRestrictedMetricFailed(const int64_t configId, const string& configPackage,
+                                         const std::optional<int32_t> configUid,
+                                         const int32_t callingUid, const InvalidQueryReason reason);
+
+    /** Report query of restricted metric failed along with an error string **/
+    void noteQueryRestrictedMetricFailed(const int64_t configId, const string& configPackage,
+                                         const std::optional<int32_t> configUid,
+                                         const int32_t callingUid, const InvalidQueryReason reason,
+                                         const string& error);
+
+    // Reports that a restricted metric fails to be inserted to database.
+    void noteRestrictedMetricInsertError(const ConfigKey& configKey, int64_t metricId);
+
+    // Reports that a restricted metric fails to create table in database.
+    void noteRestrictedMetricTableCreationError(const ConfigKey& configKey, int64_t metricId);
+
+    // Reports that a restricted metric fails to delete table in database.
+    void noteRestrictedMetricTableDeletionError(const ConfigKey& configKey, int64_t metricId);
+
+    // Reports the time it takes for a restricted metric to flush the data to the database.
+    void noteRestrictedMetricFlushLatency(const ConfigKey& configKey, int64_t metricId,
+                                          const int64_t flushLatencyNs);
+
+    // Reports that a restricted metric had a category change.
+    void noteRestrictedMetricCategoryChanged(const ConfigKey& configKey, int64_t metricId);
+
+    // Reports the time is takes to flush a restricted config to the database.
+    void noteRestrictedConfigFlushLatency(const ConfigKey& configKey,
+                                          const int64_t totalFlushLatencyNs);
+
+    // Reports the size of the internal sqlite db.
+    void noteRestrictedConfigDbSize(const ConfigKey& configKey, int64_t elapsedTimeNs,
+                                    const int64_t dbSize);
+
+    /**
+     * Records libstatssocket was not able to write into socket.
+     */
+    void noteAtomSocketLoss(const SocketLossInfo& lossInfo);
+
     /**
      * Report a new subscription has started and report the static stats about the subscription
      * config.
@@ -574,7 +733,20 @@
     /**
      * Return soft and hard atom key dimension size limits as an std::pair.
      */
-    static std::pair<size_t, size_t> getAtomDimensionKeySizeLimits(const int atomId = -1);
+    static std::pair<size_t, size_t> getAtomDimensionKeySizeLimits(int atomId,
+                                                                   size_t defaultHardLimit);
+
+    inline static int clampDimensionKeySizeLimit(int dimLimit) {
+        return std::clamp(dimLimit, kDimensionKeySizeHardLimitMin, kDimensionKeySizeHardLimitMax);
+    }
+
+    /**
+     * Return the unique identifier for the statsd stats report. This id is
+     * reset on boot.
+     */
+    inline int32_t getStatsdStatsId() const {
+        return mStatsdStatsId;
+    }
 
     /**
      * Returns true if there is recorded event queue overflow
@@ -641,6 +813,11 @@
 
     int32_t mStartTimeSec;
 
+    // Random id set using rand() during the initialization. Used to uniquely
+    // identify a session. This is more reliable than mStartTimeSec due to the
+    // unreliable nature of wall clock times.
+    const int32_t mStatsdStatsId;
+
     // Track the number of dropped entries used by the uid map.
     UidMapStats mUidMapStats;
 
@@ -650,7 +827,7 @@
 
     // Stores the stats for the configs that are no longer in use.
     // The size of the vector is capped by kMaxIceBoxSize.
-    std::list<const std::shared_ptr<ConfigStats>> mIceBox;
+    std::list<std::shared_ptr<ConfigStats>> mIceBox;
 
     // Stores the number of times a pushed atom is logged and skipped (if skipped).
     // The size of the vector is the largest pushed atom id in atoms.proto + 1. Atoms
@@ -680,6 +857,37 @@
     // The max size of this map is kMaxPushedAtomErrorStatsSize.
     std::map<int, int> mPushedAtomErrorStats;
 
+    // Stores the number of times a pushed atom was lost due to socket error.
+    // Represents counter per uid per tag per error with indication when the loss event was observed
+    // first & last time.
+    struct SocketLossStats {
+        SocketLossStats(int32_t uid, int64_t firstLossTsNanos, int64_t lastLossTsNanos)
+            : mUid(uid), mFirstLossTsNanos(firstLossTsNanos), mLastLossTsNanos(lastLossTsNanos) {
+        }
+
+        int32_t mUid;
+        int64_t mFirstLossTsNanos;
+        int64_t mLastLossTsNanos;
+        // atom loss count per error, atom id
+        struct AtomLossInfo {
+            AtomLossInfo(int32_t atomId, int32_t error, int32_t count)
+                : mAtomId(atomId), mError(error), mCount(count) {
+            }
+            int mAtomId;
+            int mError;
+            int mCount;
+        };
+        std::vector<AtomLossInfo> mLossCountPerErrorAtomId;
+    };
+    // The max size of this list is kMaxSocketLossStatsSize.
+    std::list<SocketLossStats> mSocketLossStats;
+
+    // Stores the number of times a pushed atom loss info was dropped from the stats
+    // on libstatssocket side due to guardrail hit.
+    // Represents counter per uid.
+    // The max size of this map is kMaxSocketLossStatsSize.
+    std::map<int32_t, int32_t> mSocketLossStatsOverflowCounters;
+
     // Maps metric ID to its stats. The size is capped by the number of metrics.
     std::map<int64_t, AtomMetricStats> mAtomMetricStats;
 
@@ -717,11 +925,51 @@
     // Total number of events that are lost due to queue overflow.
     int32_t mOverflowCount = 0;
 
+    // Max number of events stored into the queue seen so far.
+    int32_t mEventQueueMaxSizeObserved = 0;
+
+    // Event timestamp for associated max size hit.
+    int64_t mEventQueueMaxSizeObservedElapsedNanos = 0;
+
     // Timestamps when we detect log loss, and the number of logs lost.
     std::list<LogLossStats> mLogLossStats;
 
     std::list<int32_t> mSystemServerRestartSec;
 
+    struct RestrictedMetricQueryStats {
+        RestrictedMetricQueryStats(int32_t callingUid, int64_t configId,
+                                   const string& configPackage, std::optional<int32_t> configUid,
+                                   int64_t queryTimeNs,
+                                   std::optional<InvalidQueryReason> invalidQueryReason,
+                                   const string& error, std::optional<int64_t> queryLatencyNs)
+            : mCallingUid(callingUid),
+              mConfigId(configId),
+              mConfigPackage(configPackage),
+              mConfigUid(configUid),
+              mQueryWallTimeNs(queryTimeNs),
+              mInvalidQueryReason(invalidQueryReason),
+              mError(error),
+              mQueryLatencyNs(queryLatencyNs) {
+            mHasError = invalidQueryReason.has_value();
+        }
+        int32_t mCallingUid;
+        int64_t mConfigId;
+        string mConfigPackage;
+        std::optional<int32_t> mConfigUid;
+        int64_t mQueryWallTimeNs;
+        std::optional<InvalidQueryReason> mInvalidQueryReason;
+        bool mHasError;
+        string mError;
+        std::optional<int64_t> mQueryLatencyNs;
+    };
+    std::list<RestrictedMetricQueryStats> mRestrictedMetricQueryStats;
+
+    void noteQueryRestrictedMetricFailedLocked(const int64_t configId, const string& configPackage,
+                                               const std::optional<int32_t> configUid,
+                                               const int32_t callingUid,
+                                               const InvalidQueryReason reason,
+                                               const string& error);
+
     int32_t mSubscriptionPullThreadWakeupCount = 0;
 
     // Maps Subscription ID to the corresponding SubscriptionStats struct object.
@@ -747,7 +995,8 @@
 
     void noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec);
 
-    void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes, int32_t timeSec);
+    void noteMetricsReportSent(const ConfigKey& key, const size_t numBytes, int32_t timeSec,
+                               const int32_t reportNumber);
 
     void noteBroadcastSent(const ConfigKey& key, int32_t timeSec);
 
@@ -761,42 +1010,50 @@
 
     int getPushedAtomDropsLocked(int atomId) const;
 
+    bool hasRestrictedConfigErrors(const std::shared_ptr<ConfigStats>& configStats) const;
+
     /**
      * Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference
      * will live as long as `this`.
      */
     StatsdStats::AtomMetricStats& getAtomMetricStats(int64_t metricId);
 
-    FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd);
+    FRIEND_TEST(LogEventQueue_test, TestQueueMaxSize);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessage);
+    FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
+    FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit);
+    FRIEND_TEST(StatsdStatsTest, TestAnomalyMonitor);
+    FRIEND_TEST(StatsdStatsTest, TestAtomDroppedStats);
+    FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats);
+    FRIEND_TEST(StatsdStatsTest, TestAtomLog);
+    FRIEND_TEST(StatsdStatsTest, TestAtomLoggedAndDroppedAndSkippedStats);
+    FRIEND_TEST(StatsdStatsTest, TestAtomLoggedAndDroppedStats);
+    FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
+    FRIEND_TEST(StatsdStatsTest, TestAtomSkippedStats);
+    FRIEND_TEST(StatsdStatsTest, TestConfigRemove);
+    FRIEND_TEST(StatsdStatsTest, TestHasHitDimensionGuardrail);
     FRIEND_TEST(StatsdStatsTest, TestInvalidConfigAdd);
     FRIEND_TEST(StatsdStatsTest, TestInvalidConfigMissingMetricId);
     FRIEND_TEST(StatsdStatsTest, TestInvalidConfigOnlyMetricId);
-    FRIEND_TEST(StatsdStatsTest, TestConfigRemove);
-    FRIEND_TEST(StatsdStatsTest, TestSubStats);
-    FRIEND_TEST(StatsdStatsTest, TestAtomLog);
     FRIEND_TEST(StatsdStatsTest, TestNonPlatformAtomLog);
-    FRIEND_TEST(StatsdStatsTest, TestTimestampThreshold);
-    FRIEND_TEST(StatsdStatsTest, TestAnomalyMonitor);
-    FRIEND_TEST(StatsdStatsTest, TestSystemServerCrash);
     FRIEND_TEST(StatsdStatsTest, TestPullAtomStats);
-    FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
-    FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit);
-    FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats);
-    FRIEND_TEST(StatsdStatsTest, TestAtomSkippedStats);
-    FRIEND_TEST(StatsdStatsTest, TestAtomDroppedStats);
-    FRIEND_TEST(StatsdStatsTest, TestAtomLoggedAndDroppedStats);
-    FRIEND_TEST(StatsdStatsTest, TestAtomLoggedAndDroppedAndSkippedStats);
+    FRIEND_TEST(StatsdStatsTest, TestQueueStats);
+    FRIEND_TEST(StatsdStatsTest, TestRestrictedMetricsQueryStats);
+    FRIEND_TEST(StatsdStatsTest, TestRestrictedMetricsStats);
     FRIEND_TEST(StatsdStatsTest, TestShardOffsetProvider);
-    FRIEND_TEST(StatsdStatsTest, TestHasHitDimensionGuardrail);
-    FRIEND_TEST(StatsdStatsTest, TestSubscriptionStarted);
-    FRIEND_TEST(StatsdStatsTest, TestSubscriptionFlushed);
-    FRIEND_TEST(StatsdStatsTest, TestSubscriptionEnded);
+    FRIEND_TEST(StatsdStatsTest, TestSocketLossStats);
+    FRIEND_TEST(StatsdStatsTest, TestSocketLossStatsOverflowCounter);
+    FRIEND_TEST(StatsdStatsTest, TestSubStats);
     FRIEND_TEST(StatsdStatsTest, TestSubscriptionAtomPulled);
+    FRIEND_TEST(StatsdStatsTest, TestSubscriptionEnded);
+    FRIEND_TEST(StatsdStatsTest, TestSubscriptionFlushed);
     FRIEND_TEST(StatsdStatsTest, TestSubscriptionPullThreadWakeup);
+    FRIEND_TEST(StatsdStatsTest, TestSubscriptionStarted);
     FRIEND_TEST(StatsdStatsTest, TestSubscriptionStartedMaxActiveSubscriptions);
     FRIEND_TEST(StatsdStatsTest, TestSubscriptionStartedRemoveFinishedSubscription);
-
-    FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
+    FRIEND_TEST(StatsdStatsTest, TestSystemServerCrash);
+    FRIEND_TEST(StatsdStatsTest, TestTimestampThreshold);
+    FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd);
 };
 
 InvalidConfigReason createInvalidConfigReasonWithMatcher(const InvalidConfigReasonEnum reason,
@@ -831,10 +1088,10 @@
                                                               const int64_t subscriptionId);
 
 InvalidConfigReason createInvalidConfigReasonWithSubscriptionAndAlarm(
-        const InvalidConfigReasonEnum reason, const int64_t subscriptionId, const int64_t alarmId);
+        const InvalidConfigReasonEnum reason, int64_t subscriptionId, int64_t alarmId);
 
 InvalidConfigReason createInvalidConfigReasonWithSubscriptionAndAlert(
-        const InvalidConfigReasonEnum reason, const int64_t subscriptionId, const int64_t alertId);
+        const InvalidConfigReasonEnum reason, int64_t subscriptionId, int64_t alertId);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/statsd/src/guardrail/stats_log_enums.proto b/statsd/src/guardrail/stats_log_enums.proto
index b4172d3..c468d52 100644
--- a/statsd/src/guardrail/stats_log_enums.proto
+++ b/statsd/src/guardrail/stats_log_enums.proto
@@ -139,4 +139,30 @@
     INVALID_CONFIG_REASON_METRIC_DIMENSIONAL_SAMPLING_INFO_MISSING_SAMPLED_FIELD = 81;
     INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELD_INCORRECT_SIZE = 82;
     INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELDS_NOT_SUBSET_DIM_IN_WHAT = 83;
+    INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED = 84;
+    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;
+    INVALID_CONFIG_REASON_METRIC_INCORRECT_PULL_PROBABILITY = 93;
+    INVALID_CONFIG_REASON_GAUGE_METRIC_PUSHED_WITH_PULL_PROBABILITY = 94;
+    INVALID_CONFIG_REASON_GAUGE_METRIC_RANDOM_ONE_SAMPLE_WITH_PULL_PROBABILITY = 95;
+    INVALID_CONFIG_REASON_VALUE_METRIC_DEFINES_SINGLE_AND_MULTIPLE_AGG_TYPES = 96;
+    INVALID_CONFIG_REASON_VALUE_METRIC_AGG_TYPES_DNE_VALUE_FIELDS_SIZE = 97;
+};
+
+enum InvalidQueryReason {
+    UNKNOWN_REASON = 0;
+    FLAG_DISABLED = 1;
+    UNSUPPORTED_SQLITE_VERSION = 2;
+    AMBIGUOUS_CONFIG_KEY = 3;
+    CONFIG_KEY_NOT_FOUND = 4;
+    CONFIG_KEY_WITH_UNMATCHED_DELEGATE = 5;
+    QUERY_FAILURE = 6;
+    INCONSISTENT_ROW_SIZE = 7;
+    NULL_CALLBACK = 8;
 };
diff --git a/statsd/src/logd/LogEvent.cpp b/statsd/src/logd/LogEvent.cpp
index bee279d..94fe5cf 100644
--- a/statsd/src/logd/LogEvent.cpp
+++ b/statsd/src/logd/LogEvent.cpp
@@ -23,7 +23,8 @@
 #include <android/binder_ibinder.h>
 #include <private/android_filesystem_config.h>
 
-#include "annotations.h"
+#include "flags/FlagProvider.h"
+#include "stats_annotations.h"
 #include "stats_log_util.h"
 #include "statslog_statsd.h"
 
@@ -53,7 +54,7 @@
 }  // namespace
 
 LogEvent::LogEvent(int32_t uid, int32_t pid)
-    : mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) {
+    : mLogdTimestampNs(getWallClockNs()), mLogUid(uid), mLogPid(pid) {
 }
 
 LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging,
@@ -345,7 +346,7 @@
 
 void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType,
                                              std::optional<uint8_t> numElements) {
-    // Allowed types: INT
+    // Allowed types: BOOL
     if (mValues.empty() || annotationType != BOOL_TYPE || !checkPreviousValueType(INT) ||
         numElements) {
         VLOG("Atom ID %d error while parseExclusiveStateAnnotation()", mTagId);
@@ -373,7 +374,7 @@
 
 void LogEvent::parseStateNestedAnnotation(uint8_t annotationType,
                                           std::optional<uint8_t> numElements) {
-    // Allowed types: INT
+    // Allowed types: BOOL
     if (mValues.empty() || annotationType != BOOL_TYPE || !checkPreviousValueType(INT) ||
         numElements) {
         VLOG("Atom ID %d error while parseStateNestedAnnotation()", mTagId);
@@ -385,6 +386,40 @@
     mValues[mValues.size() - 1].mAnnotations.setNested(nested);
 }
 
+void LogEvent::parseRestrictionCategoryAnnotation(uint8_t annotationType) {
+    // Allowed types: INT, field value should be empty since this is atom-level annotation.
+    if (!mValues.empty() || annotationType != INT32_TYPE) {
+        mValid = false;
+        return;
+    }
+    int value = readNextValue<int32_t>();
+    // should be one of predefined category in StatsLog.java
+    switch (value) {
+        case ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC:
+        case ASTATSLOG_RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE:
+        case ASTATSLOG_RESTRICTION_CATEGORY_AUTHENTICATION:
+        case ASTATSLOG_RESTRICTION_CATEGORY_FRAUD_AND_ABUSE:
+            break;
+        default:
+            mValid = false;
+            return;
+    }
+    mRestrictionCategory = static_cast<StatsdRestrictionCategory>(value);
+    return;
+}
+
+void LogEvent::parseFieldRestrictionAnnotation(uint8_t annotationType) {
+    // Allowed types: BOOL
+    if (mValues.empty() || annotationType != BOOL_TYPE) {
+        mValid = false;
+        return;
+    }
+    // Read the value so that the rest of the event is correctly parsed
+    // TODO: store the field annotations once the metrics need to parse them.
+    readNextValue<uint8_t>();
+    return;
+}
+
 // firstUidInChainIndex is a default parameter that is only needed when parsing
 // annotations for attribution chains.
 // numElements is a default param that is only needed when parsing annotations for repeated fields
@@ -395,27 +430,50 @@
         uint8_t annotationType = readNextValue<uint8_t>();
 
         switch (annotationId) {
-            case ANNOTATION_ID_IS_UID:
+            case ASTATSLOG_ANNOTATION_ID_IS_UID:
                 parseIsUidAnnotation(annotationType, numElements);
                 break;
-            case ANNOTATION_ID_TRUNCATE_TIMESTAMP:
+            case ASTATSLOG_ANNOTATION_ID_TRUNCATE_TIMESTAMP:
                 parseTruncateTimestampAnnotation(annotationType);
                 break;
-            case ANNOTATION_ID_PRIMARY_FIELD:
+            case ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD:
                 parsePrimaryFieldAnnotation(annotationType, numElements, firstUidInChainIndex);
                 break;
-            case ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID:
+            case ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID:
                 parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex);
                 break;
-            case ANNOTATION_ID_EXCLUSIVE_STATE:
+            case ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE:
                 parseExclusiveStateAnnotation(annotationType, numElements);
                 break;
-            case ANNOTATION_ID_TRIGGER_STATE_RESET:
+            case ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET:
                 parseTriggerStateResetAnnotation(annotationType, numElements);
                 break;
-            case ANNOTATION_ID_STATE_NESTED:
+            case ASTATSLOG_ANNOTATION_ID_STATE_NESTED:
                 parseStateNestedAnnotation(annotationType, numElements);
                 break;
+            case ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY:
+                if (isAtLeastU()) {
+                    parseRestrictionCategoryAnnotation(annotationType);
+                } else {
+                    mValid = false;
+                }
+                break;
+            // Currently field restrictions are ignored, so we parse but do not store them.
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO:
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE:
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY:
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT:
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY:
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH:
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT:
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING:
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION:
+                if (isAtLeastU()) {
+                    parseFieldRestrictionAnnotation(annotationType);
+                } else {
+                    mValid = false;
+                }
+                break;
             default:
                 VLOG("Atom ID %d error while parseAnnotations() - wrong annotationId(%d)", mTagId,
                      annotationId);
diff --git a/statsd/src/logd/LogEvent.h b/statsd/src/logd/LogEvent.h
index 3c416bb..c34d34e 100644
--- a/statsd/src/logd/LogEvent.h
+++ b/statsd/src/logd/LogEvent.h
@@ -24,6 +24,7 @@
 #include <vector>
 
 #include "FieldValue.h"
+#include "utils/RestrictedPolicyManager.h"
 
 namespace android {
 namespace os {
@@ -215,7 +216,7 @@
     //
     // If the index within the atom definition is desired, do the following:
     //    const std::optional<size_t>& vectorIndex = LogEvent.getExclusiveStateFieldIndex();
-    //    if (!vectorIndex) {
+    //    if (vectorIndex) {
     //        FieldValue& v = LogEvent.getValues()[vectorIndex.value()];
     //        int atomIndex = v.mField.getPosAtDepth(0);
     //    }
@@ -262,6 +263,14 @@
      */
     LogEvent(const LogEvent&) = default;
 
+    inline StatsdRestrictionCategory getRestrictionCategory() const {
+        return mRestrictionCategory;
+    }
+
+    inline bool isRestricted() const {
+        return mRestrictionCategory != CATEGORY_NO_RESTRICTION;
+    }
+
 private:
     void parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
     void parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
@@ -285,7 +294,10 @@
     void parseTriggerStateResetAnnotation(uint8_t annotationType,
                                           std::optional<uint8_t> numElements);
     void parseStateNestedAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements);
+    void parseRestrictionCategoryAnnotation(uint8_t annotationType);
+    void parseFieldRestrictionAnnotation(uint8_t annotationType);
     bool checkPreviousValueType(Type expected);
+    bool getRestrictedMetricsFlag();
 
     /**
      * The below two variables are only valid during the execution of
@@ -362,6 +374,7 @@
     // Annotations
     bool mTruncateTimestamp = false;
     int mResetState = -1;
+    StatsdRestrictionCategory mRestrictionCategory = CATEGORY_NO_RESTRICTION;
 
     size_t mNumUidFields = 0;
 
diff --git a/statsd/src/logd/LogEventQueue.cpp b/statsd/src/logd/LogEventQueue.cpp
index 96b7f01..a83fcf8 100644
--- a/statsd/src/logd/LogEventQueue.cpp
+++ b/statsd/src/logd/LogEventQueue.cpp
@@ -39,22 +39,23 @@
     return item;
 }
 
-bool LogEventQueue::push(unique_ptr<LogEvent> item, int64_t* oldestTimestampNs) {
-    bool success;
+LogEventQueue::Result LogEventQueue::push(unique_ptr<LogEvent> item) {
+    Result result;
     {
         std::unique_lock<std::mutex> lock(mMutex);
         if (mQueue.size() < mQueueLimit) {
             mQueue.push(std::move(item));
-            success = true;
+            result.success = true;
         } else {
             // safe operation as queue must not be empty.
-            *oldestTimestampNs = mQueue.front()->GetElapsedTimestampNs();
-            success = false;
+            result.oldestTimestampNs = mQueue.front()->GetElapsedTimestampNs();
+            result.success = false;
         }
+        result.size = mQueue.size();
     }
 
     mCondition.notify_one();
-    return success;
+    return result;
 }
 
 }  // namespace statsd
diff --git a/statsd/src/logd/LogEventQueue.h b/statsd/src/logd/LogEventQueue.h
index e0e2f4e..d01f3a9 100644
--- a/statsd/src/logd/LogEventQueue.h
+++ b/statsd/src/logd/LogEventQueue.h
@@ -40,12 +40,18 @@
      */
     std::unique_ptr<LogEvent> waitPop();
 
+    struct Result {
+        bool success = false;
+        int64_t oldestTimestampNs = 0;
+        int32_t size = 0;
+    };
+
     /**
      * Puts a LogEvent ptr to the end of the queue.
      * Returns false on failure when the queue is full, and output the oldest event timestamp
-     * in the queue.
+     * in the queue. Returns true on success and new queue size.
      */
-    bool push(std::unique_ptr<LogEvent> event, int64_t* oldestTimestampNs);
+    Result push(std::unique_ptr<LogEvent> event);
 
 private:
     const size_t mQueueLimit;
@@ -55,10 +61,8 @@
 
     friend class SocketParseMessageTest;
 
-    FRIEND_TEST(SocketParseMessageTestNoFiltering, TestProcessMessageNoFiltering);
-    FRIEND_TEST(SocketParseMessageTestNoFiltering,
-                TestProcessMessageNoFilteringWithEmptySetExplicitSet);
-    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterEmptySet);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessage);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageEmptySetExplicitSet);
     FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterCompleteSet);
     FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterPartialSet);
     FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterToggle);
diff --git a/statsd/src/logd/logevent_util.cpp b/statsd/src/logd/logevent_util.cpp
new file mode 100644
index 0000000..7700545
--- /dev/null
+++ b/statsd/src/logd/logevent_util.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "logd/logevent_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+std::optional<SocketLossInfo> toSocketLossInfo(const LogEvent& event) {
+    const std::vector<FieldValue>& logEventValues = event.getValues();
+
+    // check that logEvent contains the minimum required number of values to represent
+    // SocketLossInfo atom data
+    if (logEventValues.size() < 7) {
+        // atom content is invalid
+        return std::nullopt;
+    }
+
+    SocketLossInfo result;
+
+    result.uid = event.GetUid();
+    if (logEventValues[1].mField.getPosAtDepth(0) == 2 &&
+        logEventValues[1].mValue.getType() == LONG) {
+        result.firstLossTsNanos = logEventValues[1].mValue.long_value;
+    } else {
+        // atom content is invalid
+        return std::nullopt;
+    }
+
+    if (logEventValues[2].mField.getPosAtDepth(0) == 3 &&
+        logEventValues[2].mValue.getType() == LONG) {
+        result.lastLossTsNanos = logEventValues[2].mValue.long_value;
+    } else {
+        // atom content is invalid
+        return std::nullopt;
+    }
+
+    if (logEventValues[3].mField.getPosAtDepth(0) == 4 &&
+        logEventValues[3].mValue.getType() == INT) {
+        result.overflowCounter = logEventValues[3].mValue.int_value;
+    } else {
+        // atom content is invalid
+        return std::nullopt;
+    }
+
+    // skipping uid + first & last timestamps + overflowCounter
+    const int arraysOffset = 4;
+
+    // expected to have 3 arrays of equal size
+    const size_t expectedEntriesCount = (logEventValues.size() - arraysOffset) / 3;
+
+    // first array holds errors, then come tags & the 3rd array holds counts
+    result.errors.reserve(expectedEntriesCount);
+    result.atomIds.reserve(expectedEntriesCount);
+    result.counts.reserve(expectedEntriesCount);
+
+    // scan over errors entries
+    std::vector<FieldValue>::const_iterator valuesIt = logEventValues.begin() + arraysOffset;
+    while (valuesIt != logEventValues.end() && valuesIt->mField.getPosAtDepth(0) == 5 &&
+           valuesIt->mValue.getType() == INT) {
+        result.errors.push_back(valuesIt->mValue.int_value);
+        valuesIt++;
+    }
+    if (result.errors.size() != expectedEntriesCount) {
+        // atom content is invalid
+        return std::nullopt;
+    }
+
+    while (valuesIt != logEventValues.end() && valuesIt->mField.getPosAtDepth(0) == 6 &&
+           valuesIt->mValue.getType() == INT) {
+        result.atomIds.push_back(valuesIt->mValue.int_value);
+        valuesIt++;
+    }
+    if (result.atomIds.size() != expectedEntriesCount) {
+        // atom content is invalid
+        return std::nullopt;
+    }
+
+    while (valuesIt != logEventValues.end() && valuesIt->mField.getPosAtDepth(0) == 7 &&
+           valuesIt->mValue.getType() == INT) {
+        result.counts.push_back(valuesIt->mValue.int_value);
+        valuesIt++;
+    }
+    if (result.counts.size() != expectedEntriesCount) {
+        // atom content is invalid
+        return std::nullopt;
+    }
+
+    if (valuesIt != logEventValues.end()) {
+        // atom content is invalid, some extra values are present
+        return std::nullopt;
+    }
+
+    return result;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/logd/logevent_util.h b/statsd/src/logd/logevent_util.h
new file mode 100644
index 0000000..d03e071
--- /dev/null
+++ b/statsd/src/logd/logevent_util.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+
+#include "logd/LogEvent.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+struct SocketLossInfo {
+    int32_t uid;
+    int64_t firstLossTsNanos;
+    int64_t lastLossTsNanos;
+    int32_t overflowCounter;
+
+    std::vector<int32_t> errors;
+    std::vector<int32_t> atomIds;
+    std::vector<int32_t> counts;
+};
+
+// helper API to parse LogEvent into SocketLossInfo;
+std::optional<SocketLossInfo> toSocketLossInfo(const LogEvent& event);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/main.cpp b/statsd/src/main.cpp
index d8d0856..5a1f6a1 100644
--- a/statsd/src/main.cpp
+++ b/statsd/src/main.cpp
@@ -23,6 +23,7 @@
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 #include <stdio.h>
+#include <sys/random.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -66,6 +67,18 @@
     sigaction(SIGTERM, &sa, nullptr);
 }
 
+void initSeedRandom() {
+    unsigned int seed = 0;
+    // getrandom() reads bytes from urandom source into buf. If getrandom()
+    // is unable to read from urandom source, then it returns -1 and we set
+    // out seed to be time(nullptr) as a fallback.
+    if (TEMP_FAILURE_RETRY(
+                getrandom(static_cast<void*>(&seed), sizeof(unsigned int), GRND_NONBLOCK)) < 0) {
+        seed = time(nullptr);
+    }
+    srand(seed);
+}
+
 int main(int /*argc*/, char** /*argv*/) {
     // Set up the looper
     sp<Looper> looper(Looper::prepare(0 /* opts */));
@@ -88,6 +101,7 @@
                                        STATSD_INIT_COMPLETED_NO_DELAY_FLAG, FLAG_FALSE)
                                        ? 0
                                        : StatsService::kStatsdInitDelaySecs;
+    initSeedRandom();
     // Create the service
     gStatsService =
             SharedRefBase::make<StatsService>(uidMap, eventQueue, logEventFilter, initEventDelay);
diff --git a/statsd/src/matchers/AtomMatchingTracker.h b/statsd/src/matchers/AtomMatchingTracker.h
index 3e35d9b..49c35b2 100644
--- a/statsd/src/matchers/AtomMatchingTracker.h
+++ b/statsd/src/matchers/AtomMatchingTracker.h
@@ -32,14 +32,20 @@
 namespace os {
 namespace statsd {
 
+struct MatcherInitResult {
+    optional<InvalidConfigReason> invalidConfigReason;
+    bool hasStringTransformation;
+};
+
 class AtomMatchingTracker : public virtual RefBase {
 public:
-    AtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash)
-        : mId(id), mIndex(index), mInitialized(false), mProtoHash(protoHash){};
+    AtomMatchingTracker(const int64_t id, const uint64_t protoHash)
+        : mId(id), mInitialized(false), mProtoHash(protoHash){};
 
     virtual ~AtomMatchingTracker(){};
 
     // Initialize this AtomMatchingTracker.
+    // matcherIndex: index of this AtomMatchingTracker in allAtomMatchingTrackers.
     // allAtomMatchers: the list of the AtomMatcher proto config. This is needed because we don't
     //                  store the proto object in memory. We only need it during initilization.
     // allAtomMatchingTrackers: the list of the AtomMatchingTracker objects. It's a one-to-one
@@ -48,30 +54,32 @@
     //                          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(
-            const std::vector<AtomMatcher>& allAtomMatchers,
+    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<bool>& stack) = 0;
+            const std::unordered_map<int64_t, int>& matcherMap, std::vector<uint8_t>& stack) = 0;
 
     // Update appropriate state on config updates. Primarily, all indices need to be updated.
     // This matcher and all of its children are guaranteed to be preserved across the update.
     // matcher: the AtomMatcher proto from the config.
-    // index: the index of this matcher in mAllAtomMatchingTrackers.
     // atomMatchingTrackerMap: map from matcher id to index in mAllAtomMatchingTrackers
     virtual optional<InvalidConfigReason> onConfigUpdated(
-            const AtomMatcher& matcher, const int index,
+            const AtomMatcher& matcher,
             const std::unordered_map<int64_t, int>& atomMatchingTrackerMap) = 0;
 
     // Called when a log event comes.
     // event: the log event.
+    // matcherIndex: index of this AtomMatchingTracker in allAtomMatchingTrackers.
     // allAtomMatchingTrackers: the list of all AtomMatchingTrackers. This is needed because the log
     //                          processing is done recursively.
     // 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.
-    virtual void onLogEvent(const LogEvent& event,
+    // 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
@@ -96,9 +104,6 @@
     // Name of this matching. We don't really need the name, but it makes log message easy to debug.
     const int64_t mId;
 
-    // Index of this AtomMatchingTracker in MetricsManager's container.
-    int mIndex;
-
     // Whether this AtomMatchingTracker has been properly initialized.
     bool mInitialized;
 
diff --git a/statsd/src/matchers/CombinationAtomMatchingTracker.cpp b/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
index 846a530..e97188f 100644
--- a/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
+++ b/statsd/src/matchers/CombinationAtomMatchingTracker.cpp
@@ -25,52 +25,58 @@
 namespace statsd {
 
 using std::set;
+using std::shared_ptr;
 using std::unordered_map;
 using std::vector;
 
-CombinationAtomMatchingTracker::CombinationAtomMatchingTracker(const int64_t& id, const int index,
+CombinationAtomMatchingTracker::CombinationAtomMatchingTracker(const int64_t id,
                                                                const uint64_t protoHash)
-    : AtomMatchingTracker(id, index, protoHash) {
+    : AtomMatchingTracker(id, protoHash) {
 }
 
 CombinationAtomMatchingTracker::~CombinationAtomMatchingTracker() {
 }
 
-optional<InvalidConfigReason> CombinationAtomMatchingTracker::init(
-        const vector<AtomMatcher>& allAtomMatchers,
+MatcherInitResult CombinationAtomMatchingTracker::init(
+        int matcherIndex, const vector<AtomMatcher>& allAtomMatchers,
         const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-        const unordered_map<int64_t, int>& matcherMap, vector<bool>& stack) {
+        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.
-    stack[mIndex] = true;
+    stack[matcherIndex] = true;
 
-    AtomMatcher_Combination matcher = allAtomMatchers[mIndex].combination();
+    AtomMatcher_Combination matcher = allAtomMatchers[matcherIndex].combination();
 
     // 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 =
-                allAtomMatchingTrackers[childIndex]->init(allAtomMatchers, allAtomMatchingTrackers,
-                                                          matcherMap, stack);
+        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);
@@ -100,16 +115,14 @@
 
     mInitialized = true;
     // unmark this node in the recursion stack.
-    stack[mIndex] = false;
-    return nullopt;
+    stack[matcherIndex] = false;
+    return result;
 }
 
 optional<InvalidConfigReason> CombinationAtomMatchingTracker::onConfigUpdated(
-        const AtomMatcher& matcher, const int index,
-        const unordered_map<int64_t, int>& atomMatchingTrackerMap) {
-    mIndex = index;
+        const AtomMatcher& matcher, const unordered_map<int64_t, int>& atomMatchingTrackerMap) {
     mChildren.clear();
-    AtomMatcher_Combination combinationMatcher = matcher.combination();
+    const AtomMatcher_Combination& combinationMatcher = matcher.combination();
     for (const int64_t child : combinationMatcher.matcher()) {
         const auto& pair = atomMatchingTrackerMap.find(child);
         if (pair == atomMatchingTrackerMap.end()) {
@@ -126,15 +139,17 @@
 }
 
 void CombinationAtomMatchingTracker::onLogEvent(
-        const LogEvent& event, const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-        vector<MatchingState>& matcherResults) {
+        const LogEvent& event, int matcherIndex,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        vector<MatchingState>& matcherResults,
+        vector<shared_ptr<LogEvent>>& matcherTransformations) {
     // this event has been processed.
-    if (matcherResults[mIndex] != MatchingState::kNotComputed) {
+    if (matcherResults[matcherIndex] != MatchingState::kNotComputed) {
         return;
     }
 
     if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
-        matcherResults[mIndex] = MatchingState::kNotMatched;
+        matcherResults[matcherIndex] = MatchingState::kNotMatched;
         return;
     }
 
@@ -142,12 +157,13 @@
     for (const int childIndex : mChildren) {
         if (matcherResults[childIndex] == MatchingState::kNotComputed) {
             const sp<AtomMatchingTracker>& child = allAtomMatchingTrackers[childIndex];
-            child->onLogEvent(event, allAtomMatchingTrackers, matcherResults);
+            child->onLogEvent(event, childIndex, allAtomMatchingTrackers, matcherResults,
+                              matcherTransformations);
         }
     }
 
     bool matched = combinationMatch(mChildren, mLogicalOperation, matcherResults);
-    matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
+    matcherResults[matcherIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
 }
 
 }  // namespace statsd
diff --git a/statsd/src/matchers/CombinationAtomMatchingTracker.h b/statsd/src/matchers/CombinationAtomMatchingTracker.h
index edcbb49..9e06533 100644
--- a/statsd/src/matchers/CombinationAtomMatchingTracker.h
+++ b/statsd/src/matchers/CombinationAtomMatchingTracker.h
@@ -29,22 +29,23 @@
 // Represents a AtomMatcher_Combination in the StatsdConfig.
 class CombinationAtomMatchingTracker : public AtomMatchingTracker {
 public:
-    CombinationAtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash);
+    CombinationAtomMatchingTracker(const int64_t id, const uint64_t protoHash);
 
-    optional<InvalidConfigReason> init(
-            const std::vector<AtomMatcher>& allAtomMatchers,
-            const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-            const std::unordered_map<int64_t, int>& matcherMap, std::vector<bool>& 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, const int index,
+            const AtomMatcher& matcher,
             const std::unordered_map<int64_t, int>& atomMatchingTrackerMap) override;
 
     ~CombinationAtomMatchingTracker();
 
-    void onLogEvent(const LogEvent& event,
+    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 30a40a3..07f6f4c 100644
--- a/statsd/src/matchers/EventMatcherWizard.cpp
+++ b/statsd/src/matchers/EventMatcherWizard.cpp
@@ -19,17 +19,18 @@
 namespace os {
 namespace statsd {
 
-using std::vector;
-
-MatchingState EventMatcherWizard::matchLogEvent(const LogEvent& event, int matcher_index) {
-    if (matcher_index < 0 || matcher_index >= (int)mAllEventMatchers.size()) {
-        return MatchingState::kNotComputed;
+MatchLogEventResult EventMatcherWizard::matchLogEvent(const LogEvent& event, int matcherIndex) {
+    if (matcherIndex < 0 || matcherIndex >= (int)mAllEventMatchers.size()) {
+        return {MatchingState::kNotComputed, nullptr};
     }
     std::fill(mMatcherCache.begin(), mMatcherCache.end(), MatchingState::kNotComputed);
-    mAllEventMatchers[matcher_index]->onLogEvent(event, mAllEventMatchers, mMatcherCache);
-    return mMatcherCache[matcher_index];
+    std::fill(mMatcherTransformations.begin(), mMatcherTransformations.end(), nullptr);
+    mAllEventMatchers[matcherIndex]->onLogEvent(event, matcherIndex, mAllEventMatchers,
+                                                mMatcherCache, mMatcherTransformations);
+
+    return {mMatcherCache[matcherIndex], mMatcherTransformations[matcherIndex]};
 }
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/statsd/src/matchers/EventMatcherWizard.h b/statsd/src/matchers/EventMatcherWizard.h
index 7bd4529..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 matcher_index);
+    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 a0e30b2..db45560 100644
--- a/statsd/src/matchers/SimpleAtomMatchingTracker.cpp
+++ b/statsd/src/matchers/SimpleAtomMatchingTracker.cpp
@@ -23,14 +23,14 @@
 namespace os {
 namespace statsd {
 
+using std::shared_ptr;
 using std::unordered_map;
 using std::vector;
 
-SimpleAtomMatchingTracker::SimpleAtomMatchingTracker(const int64_t& id, const int index,
-                                                     const uint64_t protoHash,
+SimpleAtomMatchingTracker::SimpleAtomMatchingTracker(const int64_t id, const uint64_t protoHash,
                                                      const SimpleAtomMatcher& matcher,
                                                      const sp<UidMap>& uidMap)
-    : AtomMatchingTracker(id, index, protoHash), mMatcher(matcher), mUidMap(uidMap) {
+    : AtomMatchingTracker(id, protoHash), mMatcher(matcher), mUidMap(uidMap) {
     if (!matcher.has_atom_id()) {
         mInitialized = false;
     } else {
@@ -42,22 +42,31 @@
 SimpleAtomMatchingTracker::~SimpleAtomMatchingTracker() {
 }
 
-optional<InvalidConfigReason> SimpleAtomMatchingTracker::init(
-        const vector<AtomMatcher>& allAtomMatchers,
+MatcherInitResult SimpleAtomMatchingTracker::init(
+        int matcherIndex, const vector<AtomMatcher>& allAtomMatchers,
         const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-        const unordered_map<int64_t, int>& matcherMap, vector<bool>& stack) {
+        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(
-        const AtomMatcher& matcher, const int index,
-        const unordered_map<int64_t, int>& atomMatchingTrackerMap) {
-    mIndex = index;
+        const AtomMatcher& matcher, const unordered_map<int64_t, int>& atomMatchingTrackerMap) {
     // Do not need to update mMatcher since the matcher must be identical across the update.
     if (!mInitialized) {
         return createInvalidConfigReasonWithMatcher(
@@ -67,21 +76,27 @@
 }
 
 void SimpleAtomMatchingTracker::onLogEvent(
-        const LogEvent& event, const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-        vector<MatchingState>& matcherResults) {
-    if (matcherResults[mIndex] != MatchingState::kNotComputed) {
+        const LogEvent& event, int matcherIndex,
+        const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+        vector<MatchingState>& matcherResults,
+        vector<shared_ptr<LogEvent>>& matcherTransformations) {
+    if (matcherResults[matcherIndex] != MatchingState::kNotComputed) {
         VLOG("Matcher %lld already evaluated ", (long long)mId);
         return;
     }
 
     if (mAtomIds.find(event.GetTagId()) == mAtomIds.end()) {
-        matcherResults[mIndex] = MatchingState::kNotMatched;
+        matcherResults[matcherIndex] = MatchingState::kNotMatched;
         return;
     }
 
-    bool matched = matchesSimple(mUidMap, mMatcher, event);
-    matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
+    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 70585f9..641e9d8 100644
--- a/statsd/src/matchers/SimpleAtomMatchingTracker.h
+++ b/statsd/src/matchers/SimpleAtomMatchingTracker.h
@@ -30,23 +30,24 @@
 
 class SimpleAtomMatchingTracker : public AtomMatchingTracker {
 public:
-    SimpleAtomMatchingTracker(const int64_t& id, const int index, const uint64_t protoHash,
+    SimpleAtomMatchingTracker(const int64_t id, const uint64_t protoHash,
                               const SimpleAtomMatcher& matcher, const sp<UidMap>& uidMap);
 
     ~SimpleAtomMatchingTracker();
 
-    optional<InvalidConfigReason> init(
-            const std::vector<AtomMatcher>& allAtomMatchers,
-            const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
-            const std::unordered_map<int64_t, int>& matcherMap, std::vector<bool>& 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, const int index,
+            const AtomMatcher& matcher,
             const std::unordered_map<int64_t, int>& atomMatchingTrackerMap) override;
 
-    void onLogEvent(const LogEvent& event,
+    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 20450dd..2f7ec81 100644
--- a/statsd/src/matchers/matcher_util.cpp
+++ b/statsd/src/matchers/matcher_util.cpp
@@ -23,9 +23,11 @@
 #include "matchers/AtomMatchingTracker.h"
 #include "src/statsd_config.pb.h"
 #include "stats_util.h"
+#include "utils/Regex.h"
 
 using std::set;
 using std::string;
+using std::unique_ptr;
 using std::vector;
 
 namespace android {
@@ -84,24 +86,23 @@
     return matched;
 }
 
-bool tryMatchString(const sp<UidMap>& uidMap, const FieldValue& fieldValue,
-                    const string& str_match) {
+static bool tryMatchString(const sp<UidMap>& uidMap, const FieldValue& fieldValue,
+                           const string& str_match) {
     if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) {
         int uid = fieldValue.mValue.int_value;
         auto aidIt = UidMap::sAidToUidMapping.find(str_match);
         if (aidIt != UidMap::sAidToUidMapping.end()) {
             return ((int)aidIt->second) == uid;
         }
-        std::set<string> packageNames = uidMap->getAppNamesFromUid(uid, true /* normalize*/);
-        return packageNames.find(str_match) != packageNames.end();
+        return uidMap->hasApp(uid, str_match);
     } else if (fieldValue.mValue.getType() == STRING) {
         return fieldValue.mValue.str_value == str_match;
     }
     return false;
 }
 
-bool tryMatchWildcardString(const sp<UidMap>& uidMap, const FieldValue& fieldValue,
-                            const string& wildcardPattern) {
+static bool tryMatchWildcardString(const sp<UidMap>& uidMap, const FieldValue& fieldValue,
+                                   const string& wildcardPattern) {
     if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) {
         int uid = fieldValue.mValue.int_value;
         // TODO(b/236886985): replace aid/uid mapping with efficient bidirectional container
@@ -115,7 +116,7 @@
                 }
             }
         }
-        std::set<string> packageNames = uidMap->getAppNamesFromUid(uid, true /* normalize*/);
+        std::set<string> packageNames = uidMap->getAppNamesFromUid(uid, false /* normalize*/);
         for (const auto& packageName : packageNames) {
             if (fnmatch(wildcardPattern.c_str(), packageName.c_str(), 0) == 0) {
                 return true;
@@ -127,17 +128,42 @@
     return false;
 }
 
-bool matchesSimple(const sp<UidMap>& uidMap, const FieldValueMatcher& matcher,
-                   const vector<FieldValue>& values, int start, int end, int depth) {
-    if (depth > 2) {
-        ALOGE("Depth > 3 not supported");
-        return false;
+static unique_ptr<LogEvent> getTransformedEvent(const FieldValueMatcher& matcher,
+                                                const LogEvent& event, int start, int end) {
+    if (!matcher.has_replace_string()) {
+        return nullptr;
     }
 
-    if (start >= end) {
-        return false;
+    unique_ptr<Regex> re = Regex::create(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;
+        }
+        string str = fieldValue.mValue.str_value;
+        if (!re->replace(str, replacement) || str == 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 = str;
+    }
+    return transformedEvent;
+}
+
+static pair<int, int> getStartEndAtDepth(int targetField, int start, int end, int depth,
+                                         const vector<FieldValue>& values) {
     // Filter by entry field first
     int newStart = -1;
     int newEnd = end;
@@ -145,31 +171,45 @@
     // break when pos is larger than the one we are searching for.
     for (int i = start; i < end; i++) {
         int pos = values[i].mField.getPosAtDepth(depth);
-        if (pos == matcher.field()) {
+        if (pos == targetField) {
             if (newStart == -1) {
                 newStart = i;
             }
             newEnd = i + 1;
-        } else if (pos > matcher.field()) {
+        } else if (pos > targetField) {
             break;
         }
     }
 
+    return {newStart, newEnd};
+}
+
+/*
+ * Returns pairs of start-end indices in vector<FieldValue> that pariticipate in matching.
+ * The returned vector is empty if an error was encountered.
+ * If Position is ANY and value_matcher is matches_tuple, the vector contains a start/end pair
+ * corresponding for each child FieldValueMatcher in matches_tuple. For all other cases, the
+ * returned vector is of size 1.
+ *
+ * Also updates the depth reference parameter if matcher has Position specified.
+ */
+static vector<pair<int, int>> computeRanges(const FieldValueMatcher& matcher,
+                                            const vector<FieldValue>& values, int start, int end,
+                                            int& depth) {
     // Now we have zoomed in to a new range
-    start = newStart;
-    end = newEnd;
+    std::tie(start, end) = getStartEndAtDepth(matcher.field(), start, end, depth, values);
 
     if (start == -1) {
         // No such field found.
-        return false;
+        return {};
     }
 
-    vector<pair<int, int>> ranges; // the ranges are for matching ANY position
+    vector<pair<int, int>> ranges;
     if (matcher.has_position()) {
         // Repeated fields position is stored as a node in the path.
         depth++;
         if (depth > 2) {
-            return false;
+            return ranges;
         }
         switch (matcher.position()) {
             case Position::FIRST: {
@@ -196,27 +236,42 @@
                 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: {
-                // ANY means all the children matchers match in any of the sub trees, it's a match
-                newStart = start;
-                newEnd = end;
-                // Here start is guaranteed to be a valid index.
-                int currentPos = values[start].mField.getPosAtDepth(depth);
-                // Now find all sub trees ranges.
-                for (int i = start; i < end; i++) {
-                    int newPos = values[i].mField.getPosAtDepth(depth);
-                    if (newPos != currentPos) {
-                        ranges.push_back(std::make_pair(newStart, i));
-                        newStart = i;
-                        currentPos = newPos;
+                // 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.
+                    // Here start is guaranteed to be a valid index.
+                    int currentPos = values[start].mField.getPosAtDepth(depth);
+                    // Now find all sub trees ranges.
+                    for (int i = start; i < end; i++) {
+                        int newPos = values[i].mField.getPosAtDepth(depth);
+                        if (newPos != currentPos) {
+                            ranges.push_back(std::make_pair(start, i));
+                            start = i;
+                            currentPos = newPos;
+                        }
                     }
                 }
-                ranges.push_back(std::make_pair(newStart, end));
+                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;
         }
@@ -224,23 +279,60 @@
         // No position
         ranges.push_back(std::make_pair(start, end));
     }
-    // start and end are still pointing to the matched range.
+
+    return ranges;
+}
+
+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, nullptr};
+    }
+
+    if (start >= end) {
+        return {false, nullptr};
+    }
+
+    const vector<pair<int, int>> ranges =
+            computeRanges(matcher, event.getValues(), start, end, depth);
+
+    if (ranges.empty()) {
+        // No such field found.
+        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.
-            for (const auto& range : ranges) {
+            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, range.first, range.second,
-                                       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.
@@ -251,18 +343,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();
@@ -275,40 +367,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();
@@ -321,24 +413,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();
@@ -346,16 +438,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();
@@ -375,102 +467,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/CountMetricProducer.cpp b/statsd/src/metrics/CountMetricProducer.cpp
index d0e2fd0..fb5f488 100644
--- a/statsd/src/metrics/CountMetricProducer.cpp
+++ b/statsd/src/metrics/CountMetricProducer.cpp
@@ -54,6 +54,7 @@
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_IS_ACTIVE = 14;
 const int FIELD_ID_DIMENSION_GUARDRAIL_HIT = 17;
+const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
 
 // for CountMetricDataWrapper
 const int FIELD_ID_DATA = 1;
@@ -73,14 +74,17 @@
         const ConfigKey& key, const CountMetric& metric, const int conditionIndex,
         const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
         const uint64_t protoHash, const int64_t timeBaseNs, const int64_t startTimeNs,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
         const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
         const vector<int>& slicedStateAtoms,
         const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
                      protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
-                     stateGroupMap, getAppUpgradeBucketSplit(metric)),
-      mDimensionGuardrailHit(false) {
+                     stateGroupMap, getAppUpgradeBucketSplit(metric), configMetadataProvider),
+      mDimensionGuardrailHit(false),
+      mDimensionHardLimit(
+              StatsdStats::clampDimensionKeySizeLimit(metric.max_dimensions_per_bucket())) {
     if (metric.has_bucket()) {
         mBucketSizeNs =
                 TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000;
@@ -175,6 +179,7 @@
             return invalidConfigReason;
         }
     }
+
     return nullopt;
 }
 
@@ -236,6 +241,8 @@
                            mDimensionGuardrailHit);
     }
 
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
+                       (long long)byteSizeLocked());
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
 
@@ -338,11 +345,11 @@
     }
     // ===========GuardRail==============
     // 1. Report the tuple count if the tuple count > soft limit
-    if (mCurrentSlicedCounter->size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+    if (mCurrentSlicedCounter->size() >= StatsdStats::kDimensionKeySizeSoftLimit) {
         size_t newTupleCount = mCurrentSlicedCounter->size() + 1;
         StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
-        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+        if (newTupleCount > mDimensionHardLimit) {
             if (!mHasHitGuardrail) {
                 ALOGE("CountMetric %lld dropping data for dimension key %s", (long long)mMetricId,
                       newKey.toString().c_str());
@@ -397,7 +404,7 @@
 
 // When a new matched event comes in, we check if event falls into the current
 // bucket. If not, flush the old counter to past buckets and initialize the new bucket.
-void CountMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) {
+void CountMetricProducer::flushIfNeededLocked(const int64_t eventTimeNs) {
     int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
     if (eventTimeNs < currentBucketEndTimeNs) {
         return;
@@ -413,7 +420,7 @@
          (long long)mCurrentBucketStartTimeNs);
 }
 
-bool CountMetricProducer::countPassesThreshold(const int64_t& count) {
+bool CountMetricProducer::countPassesThreshold(const int64_t count) {
     if (mUploadThreshold == nullopt) {
         return true;
     }
@@ -433,8 +440,8 @@
     }
 }
 
-void CountMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
-                                                   const int64_t& nextBucketStartTimeNs) {
+void CountMetricProducer::flushCurrentBucketLocked(const int64_t eventTimeNs,
+                                                   const int64_t nextBucketStartTimeNs) {
     int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
     CountBucket info;
     info.mBucketStartNs = mCurrentBucketStartTimeNs;
diff --git a/statsd/src/metrics/CountMetricProducer.h b/statsd/src/metrics/CountMetricProducer.h
index 85ca814..757dad2 100644
--- a/statsd/src/metrics/CountMetricProducer.h
+++ b/statsd/src/metrics/CountMetricProducer.h
@@ -44,9 +44,10 @@
 class CountMetricProducer : public MetricProducer {
 public:
     CountMetricProducer(
-            const ConfigKey& key, const CountMetric& countMetric, const int conditionIndex,
+            const ConfigKey& key, const CountMetric& countMetric, int conditionIndex,
             const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
-            const uint64_t protoHash, const int64_t timeBaseNs, const int64_t startTimeNs,
+            const uint64_t protoHash, int64_t timeBaseNs, int64_t startTimeNs,
+            const wp<ConfigMetadataProvider> configMetadataProvider,
             const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
             const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
                     eventDeactivationMap = {},
@@ -81,10 +82,10 @@
     void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
 
     // Internal interface to handle condition change.
-    void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;
+    void onConditionChangedLocked(const bool conditionMet, int64_t eventTime) override;
 
     // Internal interface to handle sliced condition change.
-    void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
+    void onSlicedConditionMayChangeLocked(bool overallCondition, int64_t eventTime) override;
 
     // Internal function to calculate the current used bytes.
     size_t byteSizeLocked() const override;
@@ -94,15 +95,14 @@
     void dropDataLocked(const int64_t dropTimeNs) override;
 
     // Util function to flush the old packet.
-    void flushIfNeededLocked(const int64_t& newEventTime) override;
+    void flushIfNeededLocked(int64_t newEventTime) override;
 
-    void flushCurrentBucketLocked(const int64_t& eventTimeNs,
-                                  const int64_t& nextBucketStartTimeNs) override;
+    void flushCurrentBucketLocked(int64_t eventTimeNs, int64_t nextBucketStartTimeNs) override;
 
     void onActiveStateChangedLocked(const int64_t eventTimeNs, const bool isActive) override;
 
     optional<InvalidConfigReason> onConfigUpdatedLocked(
-            const StatsdConfig& config, const int configIndex, const int metricIndex,
+            const StatsdConfig& config, int configIndex, int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
             const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
             const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -130,11 +130,13 @@
 
     bool hitGuardRailLocked(const MetricDimensionKey& newKey);
 
-    bool countPassesThreshold(const int64_t& count);
+    bool countPassesThreshold(int64_t count);
 
     // Tracks if the dimension guardrail has been hit in the current report.
     bool mDimensionGuardrailHit;
 
+    const size_t mDimensionHardLimit;
+
     FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
@@ -145,6 +147,10 @@
 
     FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket);
     FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket);
+
+    FRIEND_TEST(MetricsManagerUtilDimLimitTest, TestDimLimit);
+
+    FRIEND_TEST(ConfigUpdateDimLimitTest, TestDimLimit);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/DurationMetricProducer.cpp b/statsd/src/metrics/DurationMetricProducer.cpp
index 745087f..e18f83c 100644
--- a/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/statsd/src/metrics/DurationMetricProducer.cpp
@@ -53,6 +53,7 @@
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_IS_ACTIVE = 14;
 const int FIELD_ID_DIMENSION_GUARDRAIL_HIT = 17;
+const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
 // for DurationMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 // for DurationMetricData
@@ -73,19 +74,22 @@
         const int startIndex, const int stopIndex, const int stopAllIndex, const bool nesting,
         const sp<ConditionWizard>& wizard, const uint64_t protoHash,
         const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
         const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
         const vector<int>& slicedStateAtoms,
         const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
                      protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
-                     stateGroupMap, getAppUpgradeBucketSplit(metric)),
+                     stateGroupMap, getAppUpgradeBucketSplit(metric), configMetadataProvider),
       mAggregationType(metric.aggregation_type()),
       mStartIndex(startIndex),
       mStopIndex(stopIndex),
       mStopAllIndex(stopAllIndex),
       mNested(nesting),
-      mContainANYPositionInInternalDimensions(false) {
+      mContainANYPositionInInternalDimensions(false),
+      mDimensionHardLimit(
+              StatsdStats::clampDimensionKeySizeLimit(metric.max_dimensions_per_bucket())) {
     if (metric.has_bucket()) {
         mBucketSizeNs =
                 TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000;
@@ -316,6 +320,7 @@
                                             const HashableDimensionKey& primaryKey,
                                             const FieldValue& oldState,
                                             const FieldValue& newState) {
+    std::lock_guard<std::mutex> lock(mMutex);
     // Check if this metric has a StateMap. If so, map the new state value to
     // the correct state group id.
     FieldValue newStateCopy = newState;
@@ -514,6 +519,8 @@
 
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
+                       (long long)byteSizeLocked());
 
     if (mPastBuckets.empty()) {
         VLOG(" Duration metric, empty return");
@@ -601,7 +608,7 @@
     }
 }
 
-void DurationMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) {
+void DurationMetricProducer::flushIfNeededLocked(const int64_t eventTimeNs) {
     int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
 
     if (currentBucketEndTimeNs > eventTimeNs) {
@@ -615,8 +622,8 @@
     mCurrentBucketNum += numBucketsForward;
 }
 
-void DurationMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
-                                                      const int64_t& nextBucketStartTimeNs) {
+void DurationMetricProducer::flushCurrentBucketLocked(const int64_t eventTimeNs,
+                                                      const int64_t nextBucketStartTimeNs) {
     const auto [globalConditionTrueNs, globalConditionCorrectionNs] =
             mConditionTimer.newBucketStart(eventTimeNs, nextBucketStartTimeNs);
 
@@ -652,16 +659,16 @@
     }
 }
 
-bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
+bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) const {
     auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat());
     if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
         // 1. Report the tuple count if the tuple count > soft limit
-        if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+        if (mCurrentSlicedDurationTrackerMap.size() >= StatsdStats::kDimensionKeySizeSoftLimit) {
             size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1;
             StatsdStats::getInstance().noteMetricDimensionSize(
                     mConfigKey, mMetricId, newTupleCount);
             // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
-            if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+            if (newTupleCount > mDimensionHardLimit) {
                 if (!mHasHitGuardrail) {
                     ALOGE("DurationMetric %lld dropping data for what dimension key %s",
                           (long long)mMetricId, newKey.getDimensionKeyInWhat().toString().c_str());
@@ -690,16 +697,18 @@
 
     auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (mUseWhatDimensionAsInternalDimension) {
-        it->second->noteStart(whatKey, condition, eventTimeNs, conditionKeys);
+        it->second->noteStart(whatKey, condition, eventTimeNs, conditionKeys, mDimensionHardLimit);
         return;
     }
 
     if (mInternalDimensions.empty()) {
-        it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, eventTimeNs, conditionKeys);
+        it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, eventTimeNs, conditionKeys,
+                              mDimensionHardLimit);
     } else {
         HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
         filterValues(mInternalDimensions, eventValues, &dimensionKey);
-        it->second->noteStart(dimensionKey, condition, eventTimeNs, conditionKeys);
+        it->second->noteStart(dimensionKey, condition, eventTimeNs, conditionKeys,
+                              mDimensionHardLimit);
     }
 }
 
diff --git a/statsd/src/metrics/DurationMetricProducer.h b/statsd/src/metrics/DurationMetricProducer.h
index b880bd1..b7bc29a 100644
--- a/statsd/src/metrics/DurationMetricProducer.h
+++ b/statsd/src/metrics/DurationMetricProducer.h
@@ -39,12 +39,12 @@
 class DurationMetricProducer : public MetricProducer {
 public:
     DurationMetricProducer(
-            const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex,
-            const vector<ConditionState>& initialConditionCache, const int whatIndex,
-            const int startIndex, const int stopIndex, const int stopAllIndex, const bool nesting,
+            const ConfigKey& key, const DurationMetric& durationMetric, int conditionIndex,
+            const vector<ConditionState>& initialConditionCache, int whatIndex,
+            const int startIndex, int stopIndex, int stopAllIndex, const bool nesting,
             const sp<ConditionWizard>& wizard, const uint64_t protoHash,
-            const FieldMatcher& internalDimensions, const int64_t timeBaseNs,
-            const int64_t startTimeNs,
+            const FieldMatcher& internalDimensions, int64_t timeBaseNs, const int64_t startTimeNs,
+            const wp<ConfigMetadataProvider> configMetadataProvider,
             const unordered_map<int, shared_ptr<Activation>>& eventActivationMap = {},
             const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap = {},
             const vector<int>& slicedStateAtoms = {},
@@ -57,7 +57,7 @@
                                          const UpdateStatus& updateStatus,
                                          const int64_t updateTimeNs) override;
 
-    void addAnomalyTracker(sp<AnomalyTracker>& anomalyTracker, const int64_t updateTimeNs) override;
+    void addAnomalyTracker(sp<AnomalyTracker>& anomalyTracker, int64_t updateTimeNs) override;
 
     void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
                         const HashableDimensionKey& primaryKey, const FieldValue& oldState,
@@ -77,13 +77,13 @@
 
 private:
     // Initializes true dimensions of the 'what' predicate. Only to be called during initialization.
-    void initTrueDimensions(const int whatIndex, const int64_t startTimeNs);
+    void initTrueDimensions(const int whatIndex, int64_t startTimeNs);
 
     void handleMatchedLogEventValuesLocked(const size_t matcherIndex,
                                            const std::vector<FieldValue>& values,
                                            const int64_t eventTimeNs);
     void handleStartEvent(const MetricDimensionKey& eventKey, const ConditionKey& conditionKeys,
-                          bool condition, const int64_t eventTimeNs,
+                          bool condition, int64_t eventTimeNs,
                           const vector<FieldValue>& eventValues);
 
     void onDumpReportLocked(const int64_t dumpTimeNs,
@@ -96,13 +96,13 @@
     void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
 
     // Internal interface to handle condition change.
-    void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;
+    void onConditionChangedLocked(const bool conditionMet, int64_t eventTime) override;
 
     // Internal interface to handle active state change.
     void onActiveStateChangedLocked(const int64_t eventTimeNs, const bool isActive) override;
 
     // Internal interface to handle sliced condition change.
-    void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
+    void onSlicedConditionMayChangeLocked(bool overallCondition, int64_t eventTime) override;
 
     void onSlicedConditionMayChangeInternalLocked(const int64_t eventTimeNs);
 
@@ -116,13 +116,12 @@
     void dropDataLocked(const int64_t dropTimeNs) override;
 
     // Util function to flush the old packet.
-    void flushIfNeededLocked(const int64_t& eventTime);
+    void flushIfNeededLocked(int64_t eventTime);
 
-    void flushCurrentBucketLocked(const int64_t& eventTimeNs,
-                                  const int64_t& nextBucketStartTimeNs) override;
+    void flushCurrentBucketLocked(int64_t eventTimeNs, int64_t nextBucketStartTimeNs) override;
 
     optional<InvalidConfigReason> onConfigUpdatedLocked(
-            const StatsdConfig& config, const int configIndex, const int metricIndex,
+            const StatsdConfig& config, int configIndex, int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
             const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
             const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -138,7 +137,7 @@
             std::vector<int>& metricsWithActivation) override;
 
     void addAnomalyTrackerLocked(sp<AnomalyTracker>& anomalyTracker,
-                                 const UpdateStatus& updateStatus, const int64_t updateTimeNs);
+                                 const UpdateStatus& updateStatus, int64_t updateTimeNs);
 
     const DurationMetric_AggregationType mAggregationType;
 
@@ -172,19 +171,20 @@
     std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
             mCurrentSlicedDurationTrackerMap;
 
+    const size_t mDimensionHardLimit;
+
     // Helper function to create a duration tracker given the metric aggregation type.
     std::unique_ptr<DurationTracker> createDurationTracker(
             const MetricDimensionKey& eventKey) const;
 
     // Util function to check whether the specified dimension hits the guardrail.
-    bool hitGuardRailLocked(const MetricDimensionKey& newKey);
+    bool hitGuardRailLocked(const MetricDimensionKey& newKey) const;
 
     static const size_t kBucketSize = sizeof(DurationBucket{});
 
     FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition);
     FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition);
     FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState);
-    FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates);
     FRIEND_TEST(DurationMetricTrackerTest, TestFirstBucket);
 
     FRIEND_TEST(DurationMetricProducerTest, TestSumDurationAppUpgradeSplitDisabled);
@@ -197,6 +197,10 @@
 
     FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts);
+
+    FRIEND_TEST(MetricsManagerUtilDimLimitTest, TestDimLimit);
+
+    FRIEND_TEST(ConfigUpdateDimLimitTest, TestDimLimit);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/EventMetricProducer.cpp b/statsd/src/metrics/EventMetricProducer.cpp
index dd67919..a8f4539 100644
--- a/statsd/src/metrics/EventMetricProducer.cpp
+++ b/statsd/src/metrics/EventMetricProducer.cpp
@@ -48,6 +48,7 @@
 const int FIELD_ID_ID = 1;
 const int FIELD_ID_EVENT_METRICS = 4;
 const int FIELD_ID_IS_ACTIVE = 14;
+const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
 // for EventMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 // for EventMetricData
@@ -60,13 +61,15 @@
         const ConfigKey& key, const EventMetric& metric, const int conditionIndex,
         const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
         const uint64_t protoHash, const int64_t startTimeNs,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
         const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
         const vector<int>& slicedStateAtoms,
         const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
     : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, initialConditionCache, wizard,
                      protoHash, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
-                     stateGroupMap, /*splitBucketForAppUpgrade=*/nullopt) {
+                     stateGroupMap, /*splitBucketForAppUpgrade=*/nullopt, configMetadataProvider),
+      mSamplingPercentage(metric.sampling_percentage()) {
     if (metric.links().size() > 0) {
         for (const auto& link : metric.links()) {
             Metric2Condition mc;
@@ -77,6 +80,7 @@
         }
         mConditionSliced = true;
     }
+
     mTotalSize = 0;
     VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)mMetricId,
          (long long)mBucketSizeNs, (long long)mTimeBaseNs);
@@ -171,6 +175,9 @@
                                              ProtoOutputStream* protoOutput) {
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
+                       (long long)byteSizeLocked());
+
     uint64_t protoToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_EVENT_METRICS);
     for (const auto& [atomDimensionKey, elapsedTimestampsNs] : mAggregatedAtoms) {
         uint64_t wrapperToken =
@@ -211,6 +218,10 @@
         return;
     }
 
+    if (mSamplingPercentage < 100 && !shouldKeepRandomSample(mSamplingPercentage)) {
+        return;
+    }
+
     const int64_t elapsedTimeNs = truncateTimestampIfNecessary(event);
     AtomDimensionKey key(event.GetTagId(), HashableDimensionKey(event.getValues()));
 
diff --git a/statsd/src/metrics/EventMetricProducer.h b/statsd/src/metrics/EventMetricProducer.h
index f29becd..aed4358 100644
--- a/statsd/src/metrics/EventMetricProducer.h
+++ b/statsd/src/metrics/EventMetricProducer.h
@@ -35,9 +35,10 @@
 class EventMetricProducer : public MetricProducer {
 public:
     EventMetricProducer(
-            const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex,
+            const ConfigKey& key, const EventMetric& eventMetric, int conditionIndex,
             const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
-            const uint64_t protoHash, const int64_t startTimeNs,
+            const uint64_t protoHash, int64_t startTimeNs,
+            const wp<ConfigMetadataProvider> configMetadataProvider,
             const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
             const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
                     eventDeactivationMap = {},
@@ -50,6 +51,9 @@
         return METRIC_TYPE_EVENT;
     }
 
+protected:
+    size_t mTotalSize;
+
 private:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -65,13 +69,13 @@
     void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
 
     // Internal interface to handle condition change.
-    void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;
+    void onConditionChangedLocked(const bool conditionMet, int64_t eventTime) override;
 
     // Internal interface to handle sliced condition change.
-    void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
+    void onSlicedConditionMayChangeLocked(bool overallCondition, int64_t eventTime) override;
 
     optional<InvalidConfigReason> onConfigUpdatedLocked(
-            const StatsdConfig& config, const int configIndex, const int metricIndex,
+            const StatsdConfig& config, int configIndex, int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
             const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
             const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -96,7 +100,7 @@
     // Maps the field/value pairs of an atom to a list of timestamps used to deduplicate atoms.
     std::unordered_map<AtomDimensionKey, std::vector<int64_t>> mAggregatedAtoms;
 
-    size_t mTotalSize;
+    const int mSamplingPercentage;
 };
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/GaugeMetricProducer.cpp b/statsd/src/metrics/GaugeMetricProducer.cpp
index 75b2d52..f82f04e 100644
--- a/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -50,6 +50,7 @@
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_IS_ACTIVE = 14;
 const int FIELD_ID_DIMENSION_GUARDRAIL_HIT = 17;
+const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
 // for GaugeMetricDataWrapper
 const int FIELD_ID_DATA = 1;
 const int FIELD_ID_SKIPPED = 2;
@@ -80,12 +81,14 @@
         const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int triggerAtomId,
         const int atomId, const int64_t timeBaseNs, const int64_t startTimeNs,
         const sp<StatsPullerManager>& pullerManager,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
         const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
         const size_t dimensionSoftLimit, const size_t dimensionHardLimit)
     : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, initialConditionCache, wizard,
                      protoHash, eventActivationMap, eventDeactivationMap, /*slicedStateAtoms=*/{},
-                     /*stateGroupMap=*/{}, getAppUpgradeBucketSplit(metric)),
+                     /*stateGroupMap=*/{}, getAppUpgradeBucketSplit(metric),
+                     configMetadataProvider),
       mWhatMatcherIndex(whatMatcherIndex),
       mEventMatcherWizard(matcherWizard),
       mPullerManager(pullerManager),
@@ -100,7 +103,9 @@
       mDimensionSoftLimit(dimensionSoftLimit),
       mDimensionHardLimit(dimensionHardLimit),
       mGaugeAtomsPerDimensionLimit(metric.max_num_gauge_atoms_per_bucket()),
-      mDimensionGuardrailHit(false) {
+      mDimensionGuardrailHit(false),
+      mSamplingPercentage(metric.sampling_percentage()),
+      mPullProbability(metric.pull_probability()) {
     mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>();
     mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>();
     int64_t bucketSizeMills = 0;
@@ -256,6 +261,8 @@
 
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
+                       (long long)byteSizeLocked());
 
     if (mPastBuckets.empty() && mSkippedBuckets.empty()) {
         return;
@@ -393,7 +400,7 @@
         default:
             break;
     }
-    if (!triggerPuller) {
+    if (!triggerPuller || !shouldKeepRandomSample(mPullProbability)) {
         return;
     }
     vector<std::shared_ptr<LogEvent>> allData;
@@ -409,10 +416,11 @@
         return;
     }
     for (const auto& data : allData) {
-        LogEvent localCopy = *data;
-        localCopy.setElapsedTimestampNs(timestampNs);
-        if (mEventMatcherWizard->matchLogEvent(localCopy, mWhatMatcherIndex) ==
-            MatchingState::kMatched) {
+        const auto [matchResult, transformedEvent] =
+                mEventMatcherWizard->matchLogEvent(*data, mWhatMatcherIndex);
+        if (matchResult == MatchingState::kMatched) {
+            LogEvent localCopy = transformedEvent == nullptr ? *data : *transformedEvent;
+            localCopy.setElapsedTimestampNs(timestampNs);
             onMatchedLogEventLocked(mWhatMatcherIndex, localCopy);
         }
     }
@@ -501,9 +509,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);
         }
     }
 }
@@ -513,7 +523,7 @@
         return false;
     }
     // 1. Report the tuple count if the tuple count > soft limit
-    if (mCurrentSlicedBucket->size() > mDimensionSoftLimit - 1) {
+    if (mCurrentSlicedBucket->size() >= mDimensionSoftLimit) {
         size_t newTupleCount = mCurrentSlicedBucket->size() + 1;
         StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mMetricId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
@@ -539,6 +549,12 @@
     if (condition == false) {
         return;
     }
+
+    if (mPullTagId == -1 && mSamplingPercentage < 100 &&
+        !shouldKeepRandomSample(mSamplingPercentage)) {
+        return;
+    }
+
     int64_t eventTimeNs = event.GetElapsedTimestampNs();
     if (eventTimeNs < mCurrentBucketStartTimeNs) {
         VLOG("Gauge Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
@@ -617,7 +633,7 @@
 // bucket.
 // if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
 // the GaugeMetricProducer while holding the lock.
-void GaugeMetricProducer::flushIfNeededLocked(const int64_t& eventTimeNs) {
+void GaugeMetricProducer::flushIfNeededLocked(const int64_t eventTimeNs) {
     int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
 
     if (eventTimeNs < currentBucketEndTimeNs) {
@@ -636,8 +652,8 @@
          (long long)mCurrentBucketStartTimeNs);
 }
 
-void GaugeMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
-                                                   const int64_t& nextBucketStartTimeNs) {
+void GaugeMetricProducer::flushCurrentBucketLocked(const int64_t eventTimeNs,
+                                                   const int64_t nextBucketStartTimeNs) {
     int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs();
     int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs;
 
diff --git a/statsd/src/metrics/GaugeMetricProducer.h b/statsd/src/metrics/GaugeMetricProducer.h
index f607388..18e0b43 100644
--- a/statsd/src/metrics/GaugeMetricProducer.h
+++ b/statsd/src/metrics/GaugeMetricProducer.h
@@ -34,7 +34,7 @@
 namespace statsd {
 
 struct GaugeAtom {
-    GaugeAtom(std::shared_ptr<vector<FieldValue>> fields, int64_t elapsedTimeNs)
+    GaugeAtom(const std::shared_ptr<vector<FieldValue>>& fields, int64_t elapsedTimeNs)
         : mFields(fields), mElapsedTimestampNs(elapsedTimeNs) {
     }
     std::shared_ptr<vector<FieldValue>> mFields;
@@ -60,13 +60,13 @@
 class GaugeMetricProducer : public MetricProducer, public virtual PullDataReceiver {
 public:
     GaugeMetricProducer(
-            const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex,
+            const ConfigKey& key, const GaugeMetric& gaugeMetric, int conditionIndex,
             const vector<ConditionState>& initialConditionCache,
             const sp<ConditionWizard>& conditionWizard, const uint64_t protoHash,
             const int whatMatcherIndex, const sp<EventMatcherWizard>& matcherWizard,
-            const int pullTagId, const int triggerAtomId, const int atomId,
-            const int64_t timeBaseNs, const int64_t startTimeNs,
-            const sp<StatsPullerManager>& pullerManager,
+            const int pullTagId, int triggerAtomId, int atomId, const int64_t timeBaseNs,
+            int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
+            const wp<ConfigMetadataProvider> configMetadataProvider,
             const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
             const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
                     eventDeactivationMap = {},
@@ -82,11 +82,12 @@
     // Determine if metric needs to pull
     bool isPullNeeded() const override {
         std::lock_guard<std::mutex> lock(mMutex);
-        return mIsActive && (mCondition == ConditionState::kTrue);
+        return mIsActive && (mCondition == ConditionState::kTrue) &&
+               shouldKeepRandomSample(mPullProbability);
     };
 
     // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
-    void notifyAppUpgradeInternalLocked(const int64_t eventTimeNs) override {
+    void notifyAppUpgradeInternalLocked(int64_t eventTimeNs) override {
         flushLocked(eventTimeNs);
         if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE && mIsActive) {
             pullAndMatchEventsLocked(eventTimeNs);
@@ -94,9 +95,9 @@
     };
 
     // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
-    void onStatsdInitCompleted(const int64_t& eventTimeNs) override {
+    void onStatsdInitCompleted(int64_t eventTimeNs) override {
+        ATRACE_CALL();
         std::lock_guard<std::mutex> lock(mMutex);
-
         flushLocked(eventTimeNs);
         if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE && mIsActive) {
             pullAndMatchEventsLocked(eventTimeNs);
@@ -123,13 +124,13 @@
     void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
 
     // Internal interface to handle condition change.
-    void onConditionChangedLocked(const bool conditionMet, const int64_t eventTime) override;
+    void onConditionChangedLocked(const bool conditionMet, int64_t eventTime) override;
 
     // Internal interface to handle active state change.
     void onActiveStateChangedLocked(const int64_t eventTimeNs, const bool isActive) override;
 
     // Internal interface to handle sliced condition change.
-    void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
+    void onSlicedConditionMayChangeLocked(bool overallCondition, int64_t eventTime) override;
 
     // Internal function to calculate the current used bytes.
     size_t byteSizeLocked() const override;
@@ -139,10 +140,9 @@
     void dropDataLocked(const int64_t dropTimeNs) override;
 
     // Util function to flush the old packet.
-    void flushIfNeededLocked(const int64_t& eventTime) override;
+    void flushIfNeededLocked(int64_t eventTime) override;
 
-    void flushCurrentBucketLocked(const int64_t& eventTimeNs,
-                                  const int64_t& nextBucketStartTimeNs) override;
+    void flushCurrentBucketLocked(int64_t eventTimeNs, int64_t nextBucketStartTimeNs) override;
 
     void prepareFirstBucketLocked() override;
 
@@ -150,7 +150,7 @@
     void pullAndMatchEventsLocked(const int64_t timestampNs);
 
     optional<InvalidConfigReason> onConfigUpdatedLocked(
-            const StatsdConfig& config, const int configIndex, const int metricIndex,
+            const StatsdConfig& config, int configIndex, int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
             const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
             const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -227,6 +227,10 @@
     // Tracks if the dimension guardrail has been hit in the current report.
     bool mDimensionGuardrailHit;
 
+    const int mSamplingPercentage;
+
+    const int mPullProbability;
+
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition);
@@ -242,6 +246,10 @@
     FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled);
 
     FRIEND_TEST(ConfigUpdateTest, TestUpdateGaugeMetrics);
+
+    FRIEND_TEST(MetricsManagerUtilDimLimitTest, TestDimLimit);
+
+    FRIEND_TEST(ConfigUpdateDimLimitTest, TestDimLimit);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/KllMetricProducer.cpp b/statsd/src/metrics/KllMetricProducer.cpp
index 91f22f4..26aafa4 100644
--- a/statsd/src/metrics/KllMetricProducer.cpp
+++ b/statsd/src/metrics/KllMetricProducer.cpp
@@ -31,12 +31,9 @@
 using android::util::FIELD_TYPE_INT32;
 using android::util::FIELD_TYPE_MESSAGE;
 using android::util::ProtoOutputStream;
-using std::map;
 using std::nullopt;
 using std::optional;
-using std::shared_ptr;
 using std::string;
-using std::unordered_map;
 using zetasketch::android::AggregatorStateProto;
 
 namespace android {
@@ -61,9 +58,11 @@
                                      const ConditionOptions& conditionOptions,
                                      const StateOptions& stateOptions,
                                      const ActivationOptions& activationOptions,
-                                     const GuardrailOptions& guardrailOptions)
+                                     const GuardrailOptions& guardrailOptions,
+                                     const wp<ConfigMetadataProvider> configMetadataProvider)
     : ValueMetricProducer(metric.id(), key, protoHash, pullOptions, bucketOptions, whatOptions,
-                          conditionOptions, stateOptions, activationOptions, guardrailOptions) {
+                          conditionOptions, stateOptions, activationOptions, guardrailOptions,
+                          configMetadataProvider) {
 }
 
 KllMetricProducer::DumpProtoFields KllMetricProducer::getDumpProtoFields() const {
diff --git a/statsd/src/metrics/KllMetricProducer.h b/statsd/src/metrics/KllMetricProducer.h
index c142113..16c1e2a 100644
--- a/statsd/src/metrics/KllMetricProducer.h
+++ b/statsd/src/metrics/KllMetricProducer.h
@@ -48,7 +48,8 @@
                       const PullOptions& pullOptions, const BucketOptions& bucketOptions,
                       const WhatOptions& whatOptions, const ConditionOptions& conditionOptions,
                       const StateOptions& stateOptions, const ActivationOptions& activationOptions,
-                      const GuardrailOptions& guardrailOptions);
+                      const GuardrailOptions& guardrailOptions,
+                      const wp<ConfigMetadataProvider> configMetadataProvider);
 
     inline MetricType getMetricType() const override {
         return METRIC_TYPE_KLL;
@@ -121,6 +122,10 @@
     FRIEND_TEST(KllMetricProducerTest_PartialBucket, TestPushedEventsMultipleBuckets);
 
     FRIEND_TEST(ConfigUpdateTest, TestUpdateKllMetrics);
+
+    FRIEND_TEST(MetricsManagerUtilDimLimitTest, TestDimLimit);
+
+    FRIEND_TEST(ConfigUpdateDimLimitTest, TestDimLimit);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/MetricProducer.cpp b/statsd/src/metrics/MetricProducer.cpp
index bd69798..14c07a8 100644
--- a/statsd/src/metrics/MetricProducer.cpp
+++ b/statsd/src/metrics/MetricProducer.cpp
@@ -45,15 +45,16 @@
 const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE = 3;
 
 MetricProducer::MetricProducer(
-        const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
-        const int conditionIndex, const vector<ConditionState>& initialConditionCache,
-        const sp<ConditionWizard>& wizard, const uint64_t protoHash,
+        int64_t metricId, const ConfigKey& key, const int64_t timeBaseNs, const int conditionIndex,
+        const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+        const uint64_t protoHash,
         const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
         const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
                 eventDeactivationMap,
         const vector<int>& slicedStateAtoms,
         const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap,
-        const optional<bool> splitBucketForAppUpgrade)
+        const optional<bool> splitBucketForAppUpgrade,
+        const wp<ConfigMetadataProvider> configMetadataProvider)
     : mMetricId(metricId),
       mProtoHash(protoHash),
       mConfigKey(key),
@@ -78,7 +79,8 @@
       mSplitBucketForAppUpgrade(splitBucketForAppUpgrade),
       mHasHitGuardrail(false),
       mSampledWhatFields({}),
-      mShardCount(0) {
+      mShardCount(0),
+      mConfigMetadataProvider(configMetadataProvider) {
 }
 
 optional<InvalidConfigReason> MetricProducer::onConfigUpdatedLocked(
@@ -243,7 +245,7 @@
     if (it == mEventDeactivationMap.end()) {
         return;
     }
-    for (auto activationToCancelIt : it->second)  {
+    for (auto& activationToCancelIt : it->second) {
         activationToCancelIt->state = ActivationState::kNotActive;
     }
 }
@@ -319,7 +321,7 @@
     }
 }
 
-void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+void MetricProducer::queryStateValue(int32_t atomId, const HashableDimensionKey& queryKey,
                                      FieldValue* value) {
     if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) {
         value->mValue = Value(StateTracker::kStateUnknown);
@@ -329,7 +331,7 @@
     }
 }
 
-void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) {
+void MetricProducer::mapStateValue(int32_t atomId, FieldValue* value) {
     // check if there is a state map for this atom
     auto atomIt = mStateGroupMap.find(atomId);
     if (atomIt == mStateGroupMap.end()) {
diff --git a/statsd/src/metrics/MetricProducer.h b/statsd/src/metrics/MetricProducer.h
index c4871e8..8bf3998 100644
--- a/statsd/src/metrics/MetricProducer.h
+++ b/statsd/src/metrics/MetricProducer.h
@@ -27,13 +27,17 @@
 #include "condition/ConditionTimer.h"
 #include "condition/ConditionWizard.h"
 #include "config/ConfigKey.h"
+#include "config/ConfigMetadataProvider.h"
 #include "guardrail/StatsdStats.h"
 #include "matchers/EventMatcherWizard.h"
 #include "matchers/matcher_util.h"
 #include "packages/PackageInfoListener.h"
+#include "src/statsd_metadata.pb.h"  // MetricMetadata
 #include "state/StateListener.h"
 #include "state/StateManager.h"
+#include "utils/DbUtils.h"
 #include "utils/ShardOffsetProvider.h"
+#include "utils/api_tracing.h"
 
 namespace android {
 namespace os {
@@ -71,11 +75,12 @@
 };
 
 struct Activation {
-    Activation(const ActivationType& activationType, const int64_t ttlNs)
+    Activation(const ActivationType& activationType, int64_t ttlNs)
         : ttl_ns(ttlNs),
           start_ns(0),
           state(ActivationState::kNotActive),
-          activationType(activationType) {}
+          activationType(activationType) {
+    }
 
     const int64_t ttl_ns;
     int64_t start_ns;
@@ -125,7 +130,7 @@
 // be a no-op.
 class MetricProducer : public virtual RefBase, public virtual StateListener {
 public:
-    MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
+    MetricProducer(int64_t metricId, const ConfigKey& key, int64_t timeBaseNs,
                    const int conditionIndex, const vector<ConditionState>& initialConditionCache,
                    const sp<ConditionWizard>& wizard, const uint64_t protoHash,
                    const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
@@ -133,7 +138,8 @@
                            eventDeactivationMap,
                    const vector<int>& slicedStateAtoms,
                    const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap,
-                   const optional<bool> splitBucketForAppUpgrade);
+                   const optional<bool> splitBucketForAppUpgrade,
+                   const wp<ConfigMetadataProvider> configMetadataProvider);
 
     virtual ~MetricProducer(){};
 
@@ -147,7 +153,7 @@
     // This function also updates several maps used by metricsManager.
     // This function clears all anomaly trackers. All anomaly trackers need to be added again.
     optional<InvalidConfigReason> onConfigUpdated(
-            const StatsdConfig& config, const int configIndex, const int metricIndex,
+            const StatsdConfig& config, int configIndex, int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
             const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
             const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -173,7 +179,7 @@
     /**
      * Force a partial bucket split on app upgrade
      */
-    void notifyAppUpgrade(const int64_t& eventTimeNs) {
+    void notifyAppUpgrade(int64_t eventTimeNs) {
         std::lock_guard<std::mutex> lock(mMutex);
         const bool splitBucket =
                 mSplitBucketForAppUpgrade ? mSplitBucketForAppUpgrade.value() : false;
@@ -183,7 +189,7 @@
         notifyAppUpgradeInternalLocked(eventTimeNs);
     };
 
-    void notifyAppRemoved(const int64_t& eventTimeNs) {
+    void notifyAppRemoved(int64_t eventTimeNs) {
         // Force buckets to split on removal also.
         notifyAppUpgrade(eventTimeNs);
     };
@@ -191,22 +197,24 @@
     /**
      * Force a partial bucket split on boot complete.
      */
-    virtual void onStatsdInitCompleted(const int64_t& eventTimeNs) {
+    virtual void onStatsdInitCompleted(int64_t eventTimeNs) {
+        ATRACE_CALL();
         std::lock_guard<std::mutex> lock(mMutex);
         flushLocked(eventTimeNs);
     }
+
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
         std::lock_guard<std::mutex> lock(mMutex);
         onMatchedLogEventLocked(matcherIndex, event);
     }
 
-    void onConditionChanged(const bool condition, const int64_t eventTime) {
+    void onConditionChanged(const bool condition, int64_t eventTime) {
         std::lock_guard<std::mutex> lock(mMutex);
         onConditionChangedLocked(condition, eventTime);
     }
 
-    void onSlicedConditionMayChange(bool overallCondition, const int64_t eventTime) {
+    void onSlicedConditionMayChange(bool overallCondition, int64_t eventTime) {
         std::lock_guard<std::mutex> lock(mMutex);
         onSlicedConditionMayChangeLocked(overallCondition, eventTime);
     }
@@ -234,7 +242,7 @@
     }
 
     virtual optional<InvalidConfigReason> onConfigUpdatedLocked(
-            const StatsdConfig& config, const int configIndex, const int metricIndex,
+            const StatsdConfig& config, int configIndex, int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
             const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
             const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -305,6 +313,21 @@
     void writeActiveMetricToProtoOutputStream(
             int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
 
+    virtual void enforceRestrictedDataTtl(sqlite3* db, int64_t wallClockNs){};
+
+    virtual bool writeMetricMetadataToProto(metadata::MetricMetadata* metricMetadata) {
+        return false;
+    }
+
+    virtual void loadMetricMetadataFromProto(const metadata::MetricMetadata& metricMetadata){};
+
+    /* Called when the metric is to about to be removed from config. */
+    virtual void onMetricRemove() {
+    }
+
+    virtual void flushRestrictedData() {
+    }
+
     // Start: getters/setters
     inline int64_t getMetricId() const {
         return mMetricId;
@@ -347,7 +370,7 @@
     }
 
     /* Adds an AnomalyTracker that has already been created */
-    virtual void addAnomalyTracker(sp<AnomalyTracker>& anomalyTracker, const int64_t updateTimeNs) {
+    virtual void addAnomalyTracker(sp<AnomalyTracker>& anomalyTracker, int64_t updateTimeNs) {
         std::lock_guard<std::mutex> lock(mMutex);
         mAnomalyTrackers.push_back(anomalyTracker);
     }
@@ -362,7 +385,7 @@
     /**
      * Flushes the current bucket if the eventTime is after the current bucket's end time.
      */
-    virtual void flushIfNeededLocked(const int64_t& eventTime){};
+    virtual void flushIfNeededLocked(int64_t eventTime){};
 
     /**
      * For metrics that aggregate (ie, every metric producer except for EventMetricProducer),
@@ -374,13 +397,12 @@
      * flushIfNeededLocked or flushLocked or the app upgrade handler; the caller MUST update the
      * bucket timestamp and bucket number as needed.
      */
-    virtual void flushCurrentBucketLocked(const int64_t& eventTimeNs,
-                                          const int64_t& nextBucketStartTimeNs) {};
+    virtual void flushCurrentBucketLocked(int64_t eventTimeNs, int64_t nextBucketStartTimeNs){};
 
     /**
      * Flushes all the data including the current partial bucket.
      */
-    void flushLocked(const int64_t& eventTimeNs) {
+    void flushLocked(int64_t eventTimeNs) {
         flushIfNeededLocked(eventTimeNs);
         flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
     };
@@ -411,7 +433,7 @@
 
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
-    virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0;
+    virtual void onConditionChangedLocked(const bool condition, int64_t eventTime) = 0;
     virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
                                                   const int64_t eventTime) = 0;
     virtual void onDumpReportLocked(const int64_t dumpTimeNs,
@@ -453,13 +475,12 @@
 
     // Query StateManager for original state value using the queryKey.
     // The field and value are output.
-    void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
-                         FieldValue* value);
+    void queryStateValue(int32_t atomId, const HashableDimensionKey& queryKey, FieldValue* value);
 
     // If a state map exists for the given atom, replace the original state
     // value with the group id mapped to the value.
     // If no state map exists, keep the original state value.
-    void mapStateValue(const int32_t atomId, FieldValue* value);
+    void mapStateValue(int32_t atomId, FieldValue* value);
 
     // Returns a HashableDimensionKey with unknown state value for each state
     // atom.
@@ -554,13 +575,19 @@
     std::vector<SkippedBucket> mSkippedBuckets;
 
     // If hard dimension guardrail is hit, do not spam logcat. This is a per bucket tracker.
-    bool mHasHitGuardrail;
+    mutable bool mHasHitGuardrail;
 
     // Matchers for sampled fields. Currently only one sampled dimension is supported.
     std::vector<Matcher> mSampledWhatFields;
 
     int mShardCount;
 
+    inline wp<ConfigMetadataProvider> getConfigMetadataProvider() const {
+        return mConfigMetadataProvider;
+    }
+
+    wp<ConfigMetadataProvider> mConfigMetadataProvider;
+
     FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
     FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
     FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
diff --git a/statsd/src/metrics/MetricsManager.cpp b/statsd/src/metrics/MetricsManager.cpp
index 4db21e9..9b12640 100644
--- a/statsd/src/metrics/MetricsManager.cpp
+++ b/statsd/src/metrics/MetricsManager.cpp
@@ -33,6 +33,8 @@
 #include "stats_log_util.h"
 #include "stats_util.h"
 #include "statslog_statsd.h"
+#include "utils/DbUtils.h"
+#include "utils/api_tracing.h"
 
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_INT32;
@@ -43,6 +45,7 @@
 
 using std::set;
 using std::string;
+using std::unique_ptr;
 using std::vector;
 
 namespace android {
@@ -76,13 +79,21 @@
       mPullerManager(pullerManager),
       mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(),
                           config.whitelisted_atom_ids().end()),
-      mShouldPersistHistory(config.persist_locally()) {
+      mShouldPersistHistory(config.persist_locally()),
+      mUseV2SoftMemoryCalculation(config.statsd_config_options().use_v2_soft_memory_limit()) {
+    if (!isAtLeastU() && config.has_restricted_metrics_delegate_package_name()) {
+        mInvalidConfigReason =
+                InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED);
+        return;
+    }
+    if (config.has_restricted_metrics_delegate_package_name()) {
+        mRestrictedMetricsDelegatePackageName = config.restricted_metrics_delegate_package_name();
+    }
     // Init the ttl end timestamp.
     refreshTtl(timeBaseNs);
-
     mInvalidConfigReason = initStatsdConfig(
             key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseNs, currentTimeNs, mTagIdsToMatchersMap, mAllAtomMatchingTrackers,
+            timeBaseNs, currentTimeNs, this, mTagIdsToMatchersMap, mAllAtomMatchingTrackers,
             mAtomMatchingTrackerMap, mAllConditionTrackers, mConditionTrackerMap,
             mAllMetricProducers, mMetricProducerMap, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers,
             mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
@@ -95,6 +106,7 @@
 
     createAllLogSourcesFromConfig(config);
     setMaxMetricsBytesFromConfig(config);
+    setTriggerGetDataBytesFromConfig(config);
     mPullerManager->RegisterPullUidProvider(mConfigKey, this);
 
     // Store the sub-configs used.
@@ -120,6 +132,16 @@
                                   const int64_t currentTimeNs,
                                   const sp<AlarmMonitor>& anomalyAlarmMonitor,
                                   const sp<AlarmMonitor>& periodicAlarmMonitor) {
+    if (!isAtLeastU() && config.has_restricted_metrics_delegate_package_name()) {
+        mInvalidConfigReason =
+                InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED);
+        return false;
+    }
+    if (config.has_restricted_metrics_delegate_package_name()) {
+        mRestrictedMetricsDelegatePackageName = config.restricted_metrics_delegate_package_name();
+    } else {
+        mRestrictedMetricsDelegatePackageName = nullopt;
+    }
     vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers;
     unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     vector<sp<ConditionTracker>> newConditionTrackers;
@@ -142,7 +164,7 @@
             mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
             timeBaseNs, currentTimeNs, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap,
             mAllConditionTrackers, mConditionTrackerMap, mAllMetricProducers, mMetricProducerMap,
-            mAllAnomalyTrackers, mAlertTrackerMap, mStateProtoHashes, mTagIdsToMatchersMap,
+            mAllAnomalyTrackers, mAlertTrackerMap, mStateProtoHashes, this, mTagIdsToMatchersMap,
             newAtomMatchingTrackers, newAtomMatchingTrackerMap, newConditionTrackers,
             newConditionTrackerMap, newMetricProducers, newMetricProducerMap, newAnomalyTrackers,
             newAlertTrackerMap, newPeriodicAlarmTrackers, mConditionToMetricMap,
@@ -171,6 +193,7 @@
                                config.whitelisted_atom_ids().end());
     mShouldPersistHistory = config.persist_locally();
     mPackageCertificateHashSizeBytes = config.package_certificate_hash_size_bytes();
+    mUseV2SoftMemoryCalculation = config.statsd_config_options().use_v2_soft_memory_limit();
 
     // Store the sub-configs used.
     mAnnotations.clear();
@@ -185,6 +208,7 @@
     mPullAtomPackages.clear();
     createAllLogSourcesFromConfig(config);
     setMaxMetricsBytesFromConfig(config);
+    setTriggerGetDataBytesFromConfig(config);
 
     verifyGuardrailsAndUpdateStatsdStats();
     initializeConfigActiveStatus();
@@ -193,29 +217,22 @@
 
 void MetricsManager::createAllLogSourcesFromConfig(const StatsdConfig& config) {
     // Init allowed pushed atom uids.
-    if (config.allowed_log_source_size() == 0) {
-        ALOGE("Log source allowlist is empty! This config won't get any data. Suggest adding at "
-              "least AID_SYSTEM and AID_STATSD to the allowed_log_source field.");
-        mInvalidConfigReason =
-                InvalidConfigReason(INVALID_CONFIG_REASON_LOG_SOURCE_ALLOWLIST_EMPTY);
-    } else {
-        for (const auto& source : config.allowed_log_source()) {
-            auto it = UidMap::sAidToUidMapping.find(source);
-            if (it != UidMap::sAidToUidMapping.end()) {
-                mAllowedUid.push_back(it->second);
-            } else {
-                mAllowedPkg.push_back(source);
-            }
-        }
-
-        if (mAllowedUid.size() + mAllowedPkg.size() > StatsdStats::kMaxLogSourceCount) {
-            ALOGE("Too many log sources. This is likely to be an error in the config.");
-            mInvalidConfigReason = InvalidConfigReason(INVALID_CONFIG_REASON_TOO_MANY_LOG_SOURCES);
+    for (const auto& source : config.allowed_log_source()) {
+        auto it = UidMap::sAidToUidMapping.find(source);
+        if (it != UidMap::sAidToUidMapping.end()) {
+            mAllowedUid.push_back(it->second);
         } else {
-            initAllowedLogSources();
+            mAllowedPkg.push_back(source);
         }
     }
 
+    if (mAllowedUid.size() + mAllowedPkg.size() > StatsdStats::kMaxLogSourceCount) {
+        ALOGE("Too many log sources. This is likely to be an error in the config.");
+        mInvalidConfigReason = InvalidConfigReason(INVALID_CONFIG_REASON_TOO_MANY_LOG_SOURCES);
+    } else {
+        initAllowedLogSources();
+    }
+
     // Init default allowed pull atom uids.
     int numPullPackages = 0;
     for (const string& pullSource : config.default_pull_packages()) {
@@ -253,6 +270,10 @@
 }
 
 void MetricsManager::setMaxMetricsBytesFromConfig(const StatsdConfig& config) {
+    if (!config.has_max_metrics_memory_kb()) {
+        mMaxMetricsBytes = StatsdStats::kDefaultMaxMetricsBytesPerConfig;
+        return;
+    }
     if (config.max_metrics_memory_kb() <= 0 ||
         static_cast<size_t>(config.max_metrics_memory_kb() * 1024) >
                 StatsdStats::kHardMaxMetricsBytesPerConfig) {
@@ -263,6 +284,21 @@
     }
 }
 
+void MetricsManager::setTriggerGetDataBytesFromConfig(const StatsdConfig& config) {
+    if (!config.has_soft_metrics_memory_kb()) {
+        mTriggerGetDataBytes = StatsdStats::kDefaultBytesPerConfigTriggerGetData;
+        return;
+    }
+    if (config.soft_metrics_memory_kb() <= 0 ||
+        static_cast<size_t>(config.soft_metrics_memory_kb() * 1024) >
+                StatsdStats::kHardMaxTriggerGetDataBytes) {
+        ALOGW("Memory limit ust be between 0KB and 10MB. Setting to default value (192KB).");
+        mTriggerGetDataBytes = StatsdStats::kDefaultBytesPerConfigTriggerGetData;
+    } else {
+        mTriggerGetDataBytes = config.soft_metrics_memory_kb() * 1024;
+    }
+}
+
 void MetricsManager::verifyGuardrailsAndUpdateStatsdStats() {
     // Guardrail. Reject the config if it's too big.
     if (mAllMetricProducers.size() > StatsdStats::kMaxMetricCountPerConfig) {
@@ -332,7 +368,7 @@
     return !mInvalidConfigReason.has_value();
 }
 
-void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
+void MetricsManager::notifyAppUpgrade(const int64_t eventTimeNs, const string& apk, const int uid,
                                       const int64_t version) {
     // Inform all metric producers.
     for (const auto& it : mAllMetricProducers) {
@@ -353,8 +389,7 @@
     }
 }
 
-void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
-                                      const int uid) {
+void MetricsManager::notifyAppRemoved(const int64_t eventTimeNs, const string& apk, const int uid) {
     // Inform all metric producers.
     for (const auto& it : mAllMetricProducers) {
         it->notifyAppRemoved(eventTimeNs);
@@ -374,7 +409,7 @@
     }
 }
 
-void MetricsManager::onUidMapReceived(const int64_t& eventTimeNs) {
+void MetricsManager::onUidMapReceived(const int64_t eventTimeNs) {
     // Purposefully don't inform metric producers on a new snapshot
     // because we don't need to flush partial buckets.
     // This occurs if a new user is added/removed or statsd crashes.
@@ -386,7 +421,8 @@
     initAllowedLogSources();
 }
 
-void MetricsManager::onStatsdInitCompleted(const int64_t& eventTimeNs) {
+void MetricsManager::onStatsdInitCompleted(const int64_t eventTimeNs) {
+    ATRACE_CALL();
     // Inform all metric producers.
     for (const auto& it : mAllMetricProducers) {
         it->onStatsdInitCompleted(eventTimeNs);
@@ -410,6 +446,10 @@
     return uids;
 }
 
+bool MetricsManager::useV2SoftMemoryCalculation() {
+    return mUseV2SoftMemoryCalculation;
+}
+
 void MetricsManager::dumpStates(int out, bool verbose) {
     dprintf(out, "ConfigKey %s, allowed source:", mConfigKey.ToString().c_str());
     {
@@ -434,6 +474,11 @@
                                   const bool include_current_partial_bucket, const bool erase_data,
                                   const DumpLatency dumpLatency, std::set<string>* str_set,
                                   ProtoOutputStream* protoOutput) {
+    if (hasRestrictedMetricsDelegate()) {
+        // TODO(b/268150038): report error to statsdstats
+        VLOG("Unexpected call to onDumpReport in restricted metricsmanager.");
+        return;
+    }
     VLOG("=========================Metric Reports Start==========================");
     // one StatsLogReport per MetricProduer
     for (const auto& producer : mAllMetricProducers) {
@@ -474,6 +519,13 @@
     if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) {
         return true;
     }
+
+    if (event.GetUid() == AID_ROOT ||
+        (event.GetUid() >= AID_SYSTEM && event.GetUid() < AID_SHELL)) {
+        // enable atoms logged from pre-installed Android system services
+        return true;
+    }
+
     std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex);
     if (mAllowedLogSources.find(event.GetUid()) == mAllowedLogSources.end()) {
         VLOG("log source %d not on the whitelist", event.GetUid());
@@ -482,68 +534,6 @@
     return true;
 }
 
-bool MetricsManager::eventSanityCheck(const LogEvent& event) {
-    if (event.GetTagId() == util::APP_BREADCRUMB_REPORTED) {
-        // Check that app breadcrumb reported fields are valid.
-        status_t err = NO_ERROR;
-
-        // Uid is 3rd from last field and must match the caller's uid,
-        // unless that caller is statsd itself (statsd is allowed to spoof uids).
-        long appHookUid = event.GetLong(event.size()-2, &err);
-        if (err != NO_ERROR) {
-            VLOG("APP_BREADCRUMB_REPORTED had error when parsing the uid");
-            return false;
-        }
-
-        // Because the uid within the LogEvent may have been mapped from
-        // isolated to host, map the loggerUid similarly before comparing.
-        int32_t loggerUid = mUidMap->getHostUidOrSelf(event.GetUid());
-        if (loggerUid != appHookUid && loggerUid != AID_STATSD) {
-            VLOG("APP_BREADCRUMB_REPORTED has invalid uid: claimed %ld but caller is %d",
-                 appHookUid, loggerUid);
-            return false;
-        }
-
-        // The state must be from 0,3. This part of code must be manually updated.
-        long appHookState = event.GetLong(event.size(), &err);
-        if (err != NO_ERROR) {
-            VLOG("APP_BREADCRUMB_REPORTED had error when parsing the state field");
-            return false;
-        } else if (appHookState < 0 || appHookState > 3) {
-            VLOG("APP_BREADCRUMB_REPORTED does not have valid state %ld", appHookState);
-            return false;
-        }
-    } else if (event.GetTagId() == util::DAVEY_OCCURRED) {
-        // Daveys can be logged from any app since they are logged in libs/hwui/JankTracker.cpp.
-        // Check that the davey duration is reasonable. Max length check is for privacy.
-        status_t err = NO_ERROR;
-
-        // Uid is the first field provided.
-        long jankUid = event.GetLong(1, &err);
-        if (err != NO_ERROR) {
-            VLOG("Davey occurred had error when parsing the uid");
-            return false;
-        }
-        int32_t loggerUid = event.GetUid();
-        if (loggerUid != jankUid && loggerUid != AID_STATSD) {
-            VLOG("DAVEY_OCCURRED has invalid uid: claimed %ld but caller is %d", jankUid,
-                 loggerUid);
-            return false;
-        }
-
-        long duration = event.GetLong(event.size(), &err);
-        if (err != NO_ERROR) {
-            VLOG("Davey occurred had error when parsing the duration");
-            return false;
-        } else if (duration > 100000) {
-            VLOG("Davey duration is unreasonably long: %ld", duration);
-            return false;
-        }
-    }
-
-    return true;
-}
-
 // Consume the stats log if it's interesting to this metric.
 void MetricsManager::onLogEvent(const LogEvent& event) {
     if (!isConfigValid()) {
@@ -554,11 +544,6 @@
         return;
     }
 
-    // TODO(b/212755214): this check could be done once on the StatsLogProcessor level
-    if (!eventSanityCheck(event)) {
-        return;
-    }
-
     const int tagId = event.GetTagId();
     const int64_t eventTimeNs = event.GetElapsedTimestampNs();
 
@@ -598,10 +583,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, mAllAtomMatchingTrackers,
-                                                           matcherCache);
+        mAllAtomMatchingTrackers[matcherIndex]->onLogEvent(event, matcherIndex,
+                                                           mAllAtomMatchingTrackers, matcherCache,
+                                                           matcherTransformations);
     }
 
     // Set of metrics that received an activation cancellation.
@@ -642,13 +629,16 @@
     mIsActive = isActive;
 
     // A bitmap to see which ConditionTracker needs to be re-evaluated.
-    vector<bool> conditionToBeEvaluated(mAllConditionTrackers.size(), false);
+    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];
             }
         }
     }
@@ -656,59 +646,65 @@
     vector<ConditionState> conditionCache(mAllConditionTrackers.size(),
                                           ConditionState::kNotEvaluated);
     // A bitmap to track if a condition has changed value.
-    vector<bool> changedCache(mAllConditionTrackers.size(), false);
+    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);
             }
         }
     }
-
     // For matched AtomMatchers, tell relevant metrics that a matched event has come.
     for (size_t i = 0; i < mAllAtomMatchingTrackers.size(); i++) {
         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);
             }
         }
     }
 }
 
 void MetricsManager::onAnomalyAlarmFired(
-        const int64_t& timestampNs,
+        const int64_t timestampNs,
         unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet) {
     for (const auto& itr : mAllAnomalyTrackers) {
         itr->informAlarmsFired(timestampNs, alarmSet);
@@ -716,7 +712,7 @@
 }
 
 void MetricsManager::onPeriodicAlarmFired(
-        const int64_t& timestampNs,
+        const int64_t timestampNs,
         unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet) {
     for (const auto& itr : mAllPeriodicAlarmTrackers) {
         itr->informAlarmsFired(timestampNs, alarmSet);
@@ -784,6 +780,15 @@
         }
         metadataWritten |= alertWritten;
     }
+
+    for (const auto& metricProducer : mAllMetricProducers) {
+        metadata::MetricMetadata* metricMetadata = statsMetadata->add_metric_metadata();
+        bool metricWritten = metricProducer->writeMetricMetadataToProto(metricMetadata);
+        if (!metricWritten) {
+            statsMetadata->mutable_metric_metadata()->RemoveLast();
+        }
+        metadataWritten |= metricWritten;
+    }
     return metadataWritten;
 }
 
@@ -792,7 +797,7 @@
                                   int64_t systemElapsedTimeNs) {
     for (const metadata::AlertMetadata& alertMetadata : metadata.alert_metadata()) {
         int64_t alertId = alertMetadata.alert_id();
-        auto it = mAlertTrackerMap.find(alertId);
+        const auto& it = mAlertTrackerMap.find(alertId);
         if (it == mAlertTrackerMap.end()) {
             ALOGE("No anomalyTracker found for alertId %lld", (long long) alertId);
             continue;
@@ -801,6 +806,61 @@
                                                            currentWallClockTimeNs,
                                                            systemElapsedTimeNs);
     }
+    for (const metadata::MetricMetadata& metricMetadata : metadata.metric_metadata()) {
+        int64_t metricId = metricMetadata.metric_id();
+        const auto& it = mMetricProducerMap.find(metricId);
+        if (it == mMetricProducerMap.end()) {
+            ALOGE("No metricProducer found for metricId %lld", (long long)metricId);
+        }
+        mAllMetricProducers[it->second]->loadMetricMetadataFromProto(metricMetadata);
+    }
+}
+
+void MetricsManager::enforceRestrictedDataTtls(const int64_t wallClockNs) {
+    if (!hasRestrictedMetricsDelegate()) {
+        return;
+    }
+    sqlite3* db = dbutils::getDb(mConfigKey);
+    if (db == nullptr) {
+        ALOGE("Failed to open sqlite db");
+        dbutils::closeDb(db);
+        return;
+    }
+    for (const auto& producer : mAllMetricProducers) {
+        producer->enforceRestrictedDataTtl(db, wallClockNs);
+    }
+    dbutils::closeDb(db);
+}
+
+bool MetricsManager::validateRestrictedMetricsDelegate(const int32_t callingUid) {
+    if (!hasRestrictedMetricsDelegate()) {
+        return false;
+    }
+
+    set<int32_t> possibleUids = mUidMap->getAppUid(mRestrictedMetricsDelegatePackageName.value());
+
+    return possibleUids.find(callingUid) != possibleUids.end();
+}
+
+void MetricsManager::flushRestrictedData() {
+    if (!hasRestrictedMetricsDelegate()) {
+        return;
+    }
+    int64_t flushStartNs = getElapsedRealtimeNs();
+    for (const auto& producer : mAllMetricProducers) {
+        producer->flushRestrictedData();
+    }
+    StatsdStats::getInstance().noteRestrictedConfigFlushLatency(
+            mConfigKey, getElapsedRealtimeNs() - flushStartNs);
+}
+
+vector<int64_t> MetricsManager::getAllMetricIds() const {
+    vector<int64_t> metricIds;
+    metricIds.reserve(mMetricProducerMap.size());
+    for (const auto& [metricId, _] : mMetricProducerMap) {
+        metricIds.push_back(metricId);
+    }
+    return metricIds;
 }
 
 void MetricsManager::addAllAtomIds(LogEventFilter::AtomIdSet& allIds) const {
diff --git a/statsd/src/metrics/MetricsManager.h b/statsd/src/metrics/MetricsManager.h
index f07ee64..3b28b6a 100644
--- a/statsd/src/metrics/MetricsManager.h
+++ b/statsd/src/metrics/MetricsManager.h
@@ -23,6 +23,7 @@
 #include "anomaly/AnomalyTracker.h"
 #include "condition/ConditionTracker.h"
 #include "config/ConfigKey.h"
+#include "config/ConfigMetadataProvider.h"
 #include "external/StatsPullerManager.h"
 #include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
@@ -37,9 +38,11 @@
 namespace statsd {
 
 // A MetricsManager is responsible for managing metrics from one single config source.
-class MetricsManager : public virtual RefBase, public virtual PullUidProvider {
+class MetricsManager : public virtual RefBase,
+                       public virtual PullUidProvider,
+                       public virtual ConfigMetadataProvider {
 public:
-    MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, const int64_t timeBaseNs,
+    MetricsManager(const ConfigKey& configKey, const StatsdConfig& config, int64_t timeBaseNs,
                    const int64_t currentTimeNs, const sp<UidMap>& uidMap,
                    const sp<StatsPullerManager>& pullerManager,
                    const sp<AlarmMonitor>& anomalyAlarmMonitor,
@@ -47,8 +50,8 @@
 
     virtual ~MetricsManager();
 
-    bool updateConfig(const StatsdConfig& config, const int64_t timeBaseNs,
-                      const int64_t currentTimeNs, const sp<AlarmMonitor>& anomalyAlarmMonitor,
+    bool updateConfig(const StatsdConfig& config, int64_t timeBaseNs, const int64_t currentTimeNs,
+                      const sp<AlarmMonitor>& anomalyAlarmMonitor,
                       const sp<AlarmMonitor>& periodicAlarmMonitor);
 
     // Return whether the configuration is valid.
@@ -56,31 +59,30 @@
 
     bool checkLogCredentials(const LogEvent& event);
 
-    bool eventSanityCheck(const LogEvent& event);
-
-    void onLogEvent(const LogEvent& event);
+    virtual void onLogEvent(const LogEvent& event);
 
     void onAnomalyAlarmFired(
-        const int64_t& timestampNs,
-        unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
+            int64_t timestampNs,
+            unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
 
     void onPeriodicAlarmFired(
-        const int64_t& timestampNs,
-        unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
+            int64_t timestampNs,
+            unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>>& alarmSet);
 
-    void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
-                          const int64_t version);
+    void notifyAppUpgrade(int64_t eventTimeNs, const string& apk, int uid, int64_t version);
 
-    void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid);
+    void notifyAppRemoved(int64_t eventTimeNs, const string& apk, int uid);
 
-    void onUidMapReceived(const int64_t& eventTimeNs);
+    void onUidMapReceived(int64_t eventTimeNs);
 
-    void onStatsdInitCompleted(const int64_t& elapsedTimeNs);
+    void onStatsdInitCompleted(int64_t elapsedTimeNs);
 
     void init();
 
     vector<int32_t> getPullAtomUids(int32_t atomId) override;
 
+    bool useV2SoftMemoryCalculation() override;
+
     bool shouldWriteToDisk() const {
         return mNoReportMetricIds.size() != mAllMetricProducers.size();
     }
@@ -133,7 +135,7 @@
 
     virtual void dropData(const int64_t dropTimeNs);
 
-    virtual void onDumpReport(const int64_t dumpTimeNs, const int64_t wallClockNs,
+    virtual void onDumpReport(const int64_t dumpTimeNs, int64_t wallClockNs,
                               const bool include_current_partial_bucket, const bool erase_data,
                               const DumpLatency dumpLatency, std::set<string>* str_set,
                               android::util::ProtoOutputStream* protoOutput);
@@ -162,6 +164,27 @@
                       int64_t currentWallClockTimeNs,
                       int64_t systemElapsedTimeNs);
 
+    inline bool hasRestrictedMetricsDelegate() const {
+        return mRestrictedMetricsDelegatePackageName.has_value();
+    }
+
+    inline string getRestrictedMetricsDelegate() const {
+        return hasRestrictedMetricsDelegate() ? mRestrictedMetricsDelegatePackageName.value() : "";
+    }
+
+    inline ConfigKey getConfigKey() const {
+        return mConfigKey;
+    }
+
+    void enforceRestrictedDataTtls(const int64_t wallClockNs);
+
+    bool validateRestrictedMetricsDelegate(int32_t callingUid);
+
+    virtual void flushRestrictedData();
+
+    // Slow, should not be called in a hotpath.
+    vector<int64_t> getAllMetricIds() const;
+
     // Adds all atom ids referenced by matchers in the MetricsManager's config
     void addAllAtomIds(LogEventFilter::AtomIdSet& allIds) const;
 
@@ -170,6 +193,10 @@
         return mMaxMetricsBytes;
     }
 
+    inline size_t getTriggerGetDataBytes() const {
+        return mTriggerGetDataBytes;
+    }
+
 private:
     // For test only.
     inline int64_t getTtlEndNs() const { return mTtlEndNs; }
@@ -225,6 +252,7 @@
     std::list<std::pair<const int64_t, const int32_t>> mAnnotations;
 
     bool mShouldPersistHistory;
+    bool mUseV2SoftMemoryCalculation;
 
     // All event tags that are interesting to config metrics matchers.
     std::unordered_map<int, std::vector<int>> mTagIdsToMatchersMap;
@@ -326,16 +354,28 @@
     // Hashes of the States used in this config, keyed by the state id, used in config updates.
     std::map<int64_t, uint64_t> mStateProtoHashes;
 
+    // Optional package name of the delegate that processes restricted metrics
+    // If set, restricted metrics are only uploaded to the delegate.
+    optional<string> mRestrictedMetricsDelegatePackageName = nullopt;
+
     // Only called on config creation/update. Sets the memory limit in bytes for storing metrics.
     void setMaxMetricsBytesFromConfig(const StatsdConfig& config);
 
+    // Only called on config creation/update. Sets the soft memory limit in bytes for storing
+    // metrics.
+    void setTriggerGetDataBytesFromConfig(const StatsdConfig& config);
+
     // The memory limit in bytes for storing metrics
     size_t mMaxMetricsBytes;
 
-    FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
+    // The memory limit in bytes for triggering get data.
+    size_t mTriggerGetDataBytes;
+
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain);
+
+    FRIEND_TEST(GaugeMetricE2ePushedTest, TestDimensionalSampling);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTrigger);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTriggerWithActivation);
     FRIEND_TEST(GaugeMetricE2ePushedTest, TestMultipleFieldsForPushedEvent);
@@ -367,6 +407,8 @@
 
     FRIEND_TEST(MetricsManagerTest, TestLogSources);
     FRIEND_TEST(MetricsManagerTest, TestLogSourcesOnConfigUpdate);
+    FRIEND_TEST(MetricsManagerTest_SPlus, TestRestrictedMetricsConfig);
+    FRIEND_TEST(MetricsManagerTest_SPlus, TestRestrictedMetricsConfigUpdate);
     FRIEND_TEST(MetricsManagerUtilTest, TestSampledMetrics);
 
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
@@ -391,7 +433,6 @@
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
     FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
-    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
     FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold);
 
@@ -402,7 +443,8 @@
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
-    FRIEND_TEST(GaugeMetricE2ePushedTest, TestDimensionalSampling);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithMultipleAggTypes);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithDefaultAggType);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/NumericValueMetricProducer.cpp b/statsd/src/metrics/NumericValueMetricProducer.cpp
index 12c8c04..53a1901 100644
--- a/statsd/src/metrics/NumericValueMetricProducer.cpp
+++ b/statsd/src/metrics/NumericValueMetricProducer.cpp
@@ -22,6 +22,7 @@
 #include <limits.h>
 #include <stdlib.h>
 
+#include "FieldValue.h"
 #include "guardrail/StatsdStats.h"
 #include "metrics/parsing_utils/metrics_manager_util.h"
 #include "stats_log_util.h"
@@ -34,7 +35,6 @@
 using android::util::FIELD_TYPE_MESSAGE;
 using android::util::FIELD_TYPE_STRING;
 using android::util::ProtoOutputStream;
-using std::map;
 using std::optional;
 using std::shared_ptr;
 using std::string;
@@ -67,11 +67,13 @@
         const PullOptions& pullOptions, const BucketOptions& bucketOptions,
         const WhatOptions& whatOptions, const ConditionOptions& conditionOptions,
         const StateOptions& stateOptions, const ActivationOptions& activationOptions,
-        const GuardrailOptions& guardrailOptions)
+        const GuardrailOptions& guardrailOptions,
+        const wp<ConfigMetadataProvider> configMetadataProvider)
     : ValueMetricProducer(metric.id(), key, protoHash, pullOptions, bucketOptions, whatOptions,
-                          conditionOptions, stateOptions, activationOptions, guardrailOptions),
+                          conditionOptions, stateOptions, activationOptions, guardrailOptions,
+                          configMetadataProvider),
       mUseAbsoluteValueOnReset(metric.use_absolute_value_on_reset()),
-      mAggregationType(metric.aggregation_type()),
+      mAggregationTypes(whatOptions.aggregationTypes),
       mIncludeSampleSize(metric.has_include_sample_size()
                                  ? metric.include_sample_size()
                                  : metric.aggregation_type() == ValueMetric_AggregationType_AVG),
@@ -81,7 +83,8 @@
       mUseZeroDefaultBase(metric.use_zero_default_base()),
       mHasGlobalBase(false),
       mMaxPullDelayNs(metric.has_max_pull_delay_sec() ? metric.max_pull_delay_sec() * NS_PER_SEC
-                                                      : StatsdStats::kPullMaxDelayNs) {
+                                                      : StatsdStats::kPullMaxDelayNs),
+      mDedupedFieldMatchers(dedupFieldMatchers(whatOptions.fieldMatchers)) {
     // TODO(b/186677791): Use initializer list to initialize mUploadThreshold.
     if (metric.has_threshold()) {
         mUploadThreshold = metric.threshold();
@@ -267,15 +270,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(),
+            vector<int> valueIndices(mDedupedFieldMatchers.size(), -1);
+            const LogEvent& eventRef = transformedEvent == nullptr ? *data : *transformedEvent;
+            if (!filterValues(mDimensionsInWhat, mDedupedFieldMatchers, eventRef.getValues(),
                               dimensionsInWhat, valueIndices)) {
                 StatsdStats::getInstance().noteBadValueType(mMetricId);
             }
@@ -285,9 +290,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 +302,10 @@
         }
     } else {
         for (const auto& data : allData) {
-            LogEvent localCopy = *data;
-            if (mEventMatcherWizard->matchLogEvent(localCopy, mWhatMatcherIndex) ==
-                MatchingState::kMatched) {
+            const auto [matchResult, transformedEvent] =
+                    mEventMatcherWizard->matchLogEvent(*data, mWhatMatcherIndex);
+            if (matchResult == MatchingState::kMatched) {
+                LogEvent localCopy = transformedEvent == nullptr ? *data : *transformedEvent;
                 localCopy.setElapsedTimestampNs(eventElapsedTimeNs);
                 onMatchedLogEventLocked(mWhatMatcherIndex, localCopy);
             }
@@ -476,7 +482,7 @@
         }
 
         if (interval.hasValue()) {
-            switch (mAggregationType) {
+            switch (getAggregationTypeLocked(i)) {
                 case ValueMetric::SUM:
                     // for AVG, we add up and take average when flushing the bucket
                 case ValueMetric::AVG:
@@ -662,7 +668,7 @@
 }
 
 Value NumericValueMetricProducer::getFinalValue(const Interval& interval) const {
-    if (mAggregationType != ValueMetric::AVG) {
+    if (getAggregationTypeLocked(interval.aggIndex) != ValueMetric::AVG) {
         return interval.aggregate;
     } else {
         double sum = interval.aggregate.type == LONG ? (double)interval.aggregate.long_value
diff --git a/statsd/src/metrics/NumericValueMetricProducer.h b/statsd/src/metrics/NumericValueMetricProducer.h
index 78c414f..103f404 100644
--- a/statsd/src/metrics/NumericValueMetricProducer.h
+++ b/statsd/src/metrics/NumericValueMetricProducer.h
@@ -36,7 +36,8 @@
                                const ConditionOptions& conditionOptions,
                                const StateOptions& stateOptions,
                                const ActivationOptions& activationOptions,
-                               const GuardrailOptions& guardrailOptions);
+                               const GuardrailOptions& guardrailOptions,
+                               const wp<ConfigMetadataProvider> configMetadataProvider);
 
     // Process data pulled on bucket boundary.
     void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData, PullResult pullResult,
@@ -118,7 +119,7 @@
     bool hitFullBucketGuardRailLocked(const MetricDimensionKey& newKey);
 
     inline bool canSkipLogEventLocked(
-            const MetricDimensionKey& eventKey, const bool condition, const int64_t eventTimeNs,
+            const MetricDimensionKey& eventKey, const bool condition, int64_t eventTimeNs,
             const map<int, HashableDimensionKey>& statePrimaryKeys) const override {
         // For pushed metrics, can only skip if condition is false.
         // For pulled metrics, can only skip if metric is not diffed and condition is false or
@@ -142,12 +143,16 @@
     // Internal function to calculate the current used bytes.
     size_t byteSizeLocked() const override;
 
-    void combineValueFields(pair<LogEvent, vector<int>>& eventValues, const LogEvent& newEvent,
-                            const vector<int>& newValueIndices) const;
+    void combineValueFields(pair<LogEvent, std::vector<int>>& eventValues, const LogEvent& newEvent,
+                            const std::vector<int>& newValueIndices) const;
+
+    ValueMetric::AggregationType getAggregationTypeLocked(int index) const {
+        return mAggregationTypes.size() == 1 ? mAggregationTypes[0] : mAggregationTypes[index];
+    }
 
     const bool mUseAbsoluteValueOnReset;
 
-    const ValueMetric::AggregationType mAggregationType;
+    const std::vector<ValueMetric::AggregationType> mAggregationTypes;
 
     const bool mIncludeSampleSize;
 
@@ -171,6 +176,9 @@
 
     const int64_t mMaxPullDelayNs;
 
+    // Deduped value fields for matching.
+    const std::vector<Matcher> mDedupedFieldMatchers;
+
     // For anomaly detection.
     std::unordered_map<MetricDimensionKey, int64_t> mCurrentFullBucket;
 
@@ -232,6 +240,8 @@
     FRIEND_TEST(NumericValueMetricProducerTest,
                 TestSlicedStateWithMultipleDimensionsMissingDataInPull);
     FRIEND_TEST(NumericValueMetricProducerTest, TestUploadThreshold);
+    FRIEND_TEST(NumericValueMetricProducerTest, TestMultipleAggTypesPulled);
+    FRIEND_TEST(NumericValueMetricProducerTest, TestMultipleAggTypesPushed);
 
     FRIEND_TEST(NumericValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed);
     FRIEND_TEST(NumericValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed);
@@ -279,6 +289,10 @@
 
     FRIEND_TEST(ConfigUpdateTest, TestUpdateValueMetrics);
 
+    FRIEND_TEST(MetricsManagerUtilDimLimitTest, TestDimLimit);
+
+    FRIEND_TEST(ConfigUpdateDimLimitTest, TestDimLimit);
+
     friend class NumericValueMetricProducerTestHelper;
 };
 
diff --git a/statsd/src/metrics/RestrictedEventMetricProducer.cpp b/statsd/src/metrics/RestrictedEventMetricProducer.cpp
new file mode 100644
index 0000000..83454fb
--- /dev/null
+++ b/statsd/src/metrics/RestrictedEventMetricProducer.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define STATSD_DEBUG true
+#include "Log.h"
+
+#include "RestrictedEventMetricProducer.h"
+
+#include "stats_annotations.h"
+#include "stats_log_util.h"
+#include "utils/DbUtils.h"
+
+using std::lock_guard;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#define NS_PER_DAY (24 * 3600 * NS_PER_SEC)
+
+RestrictedEventMetricProducer::RestrictedEventMetricProducer(
+        const ConfigKey& key, const EventMetric& metric, const int conditionIndex,
+        const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+        const uint64_t protoHash, const int64_t startTimeNs,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
+        const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+    : EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, protoHash,
+                          startTimeNs, configMetadataProvider, eventActivationMap,
+                          eventDeactivationMap, slicedStateAtoms, stateGroupMap),
+      mRestrictedDataCategory(CATEGORY_UNKNOWN) {
+}
+
+void RestrictedEventMetricProducer::onMatchedLogEventInternalLocked(
+        const size_t matcherIndex, const MetricDimensionKey& eventKey,
+        const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+        const std::map<int, HashableDimensionKey>& statePrimaryKeys) {
+    if (!condition) {
+        return;
+    }
+    if (mRestrictedDataCategory != CATEGORY_UNKNOWN &&
+        mRestrictedDataCategory != event.getRestrictionCategory()) {
+        StatsdStats::getInstance().noteRestrictedMetricCategoryChanged(mConfigKey, mMetricId);
+        deleteMetricTable();
+        mLogEvents.clear();
+        mTotalSize = 0;
+    }
+    mRestrictedDataCategory = event.getRestrictionCategory();
+    mLogEvents.push_back(event);
+    mTotalSize += getSize(event.getValues()) + sizeof(event);
+}
+
+void RestrictedEventMetricProducer::onDumpReportLocked(
+        const int64_t dumpTimeNs, const bool include_current_partial_bucket, const bool erase_data,
+        const DumpLatency dumpLatency, std::set<string>* str_set,
+        android::util::ProtoOutputStream* protoOutput) {
+    VLOG("Unexpected call to onDumpReportLocked() in RestrictedEventMetricProducer");
+}
+
+void RestrictedEventMetricProducer::onMetricRemove() {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!mIsMetricTableCreated) {
+        return;
+    }
+    deleteMetricTable();
+}
+
+void RestrictedEventMetricProducer::enforceRestrictedDataTtl(sqlite3* db,
+                                                             const int64_t wallClockNs) {
+    int32_t ttlInDays = RestrictedPolicyManager::getInstance().getRestrictedCategoryTtl(
+            mRestrictedDataCategory);
+    int64_t ttlTime = wallClockNs - ttlInDays * NS_PER_DAY;
+    dbutils::flushTtl(db, mMetricId, ttlTime);
+}
+
+void RestrictedEventMetricProducer::clearPastBucketsLocked(const int64_t dumpTimeNs) {
+    VLOG("Unexpected call to clearPastBucketsLocked in RestrictedEventMetricProducer");
+}
+
+void RestrictedEventMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
+    mLogEvents.clear();
+    mTotalSize = 0;
+    StatsdStats::getInstance().noteBucketDropped(mMetricId);
+}
+
+void RestrictedEventMetricProducer::flushRestrictedData() {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mLogEvents.empty()) {
+        return;
+    }
+    int64_t flushStartNs = getElapsedRealtimeNs();
+    if (!mIsMetricTableCreated) {
+        if (!dbutils::isEventCompatible(mConfigKey, mMetricId, mLogEvents[0])) {
+            // Delete old data if schema changes
+            // TODO(b/268150038): report error to statsdstats
+            ALOGD("Detected schema change for metric %lld", (long long)mMetricId);
+            deleteMetricTable();
+        }
+        // TODO(b/271481944): add retry.
+        if (!dbutils::createTableIfNeeded(mConfigKey, mMetricId, mLogEvents[0])) {
+            ALOGE("Failed to create table for metric %lld", (long long)mMetricId);
+            StatsdStats::getInstance().noteRestrictedMetricTableCreationError(mConfigKey,
+                                                                              mMetricId);
+            return;
+        }
+        mIsMetricTableCreated = true;
+    }
+    string err;
+    if (!dbutils::insert(mConfigKey, mMetricId, mLogEvents, err)) {
+        ALOGE("Failed to insert logEvent to table for metric %lld. err=%s", (long long)mMetricId,
+              err.c_str());
+        StatsdStats::getInstance().noteRestrictedMetricInsertError(mConfigKey, mMetricId);
+    } else {
+        StatsdStats::getInstance().noteRestrictedMetricFlushLatency(
+                mConfigKey, mMetricId, getElapsedRealtimeNs() - flushStartNs);
+    }
+    mLogEvents.clear();
+    mTotalSize = 0;
+}
+
+bool RestrictedEventMetricProducer::writeMetricMetadataToProto(
+        metadata::MetricMetadata* metricMetadata) {
+    metricMetadata->set_metric_id(mMetricId);
+    metricMetadata->set_restricted_category(mRestrictedDataCategory);
+    return true;
+}
+
+void RestrictedEventMetricProducer::loadMetricMetadataFromProto(
+        const metadata::MetricMetadata& metricMetadata) {
+    mRestrictedDataCategory =
+            static_cast<StatsdRestrictionCategory>(metricMetadata.restricted_category());
+}
+
+void RestrictedEventMetricProducer::deleteMetricTable() {
+    if (!dbutils::deleteTable(mConfigKey, mMetricId)) {
+        StatsdStats::getInstance().noteRestrictedMetricTableDeletionError(mConfigKey, mMetricId);
+        VLOG("Failed to delete table for metric %lld", (long long)mMetricId);
+    }
+    mIsMetricTableCreated = false;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/metrics/RestrictedEventMetricProducer.h b/statsd/src/metrics/RestrictedEventMetricProducer.h
new file mode 100644
index 0000000..7f9a5eb
--- /dev/null
+++ b/statsd/src/metrics/RestrictedEventMetricProducer.h
@@ -0,0 +1,68 @@
+#ifndef RESTRICTED_EVENT_METRIC_PRODUCER_H
+#define RESTRICTED_EVENT_METRIC_PRODUCER_H
+
+#include <gtest/gtest_prod.h>
+
+#include "EventMetricProducer.h"
+#include "utils/RestrictedPolicyManager.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class RestrictedEventMetricProducer : public EventMetricProducer {
+public:
+    RestrictedEventMetricProducer(
+            const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex,
+            const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+            const uint64_t protoHash, int64_t startTimeNs,
+            const wp<ConfigMetadataProvider> configMetadataProvider,
+            const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+            const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+                    eventDeactivationMap = {},
+            const vector<int>& slicedStateAtoms = {},
+            const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
+
+    void onMetricRemove() override;
+
+    void enforceRestrictedDataTtl(sqlite3* db, int64_t wallClockNs);
+
+    void flushRestrictedData() override;
+
+    bool writeMetricMetadataToProto(metadata::MetricMetadata* metricMetadata) override;
+
+    void loadMetricMetadataFromProto(const metadata::MetricMetadata& metricMetadata) override;
+
+    inline StatsdRestrictionCategory getRestrictionCategory() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mRestrictedDataCategory;
+    }
+
+private:
+    void onMatchedLogEventInternalLocked(
+            const size_t matcherIndex, const MetricDimensionKey& eventKey,
+            const ConditionKey& conditionKey, bool condition, const LogEvent& event,
+            const std::map<int, HashableDimensionKey>& statePrimaryKeys) override;
+
+    void onDumpReportLocked(const int64_t dumpTimeNs, const bool include_current_partial_bucket,
+                            const bool erase_data, const DumpLatency dumpLatency,
+                            std::set<string>* str_set,
+                            android::util::ProtoOutputStream* protoOutput) override;
+
+    void clearPastBucketsLocked(const int64_t dumpTimeNs) override;
+
+    void dropDataLocked(const int64_t dropTimeNs) override;
+
+    void deleteMetricTable();
+
+    bool mIsMetricTableCreated = false;
+
+    StatsdRestrictionCategory mRestrictedDataCategory;
+
+    vector<LogEvent> mLogEvents;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // RESTRICTED_EVENT_METRIC_PRODUCER_H
diff --git a/statsd/src/metrics/ValueMetricProducer.cpp b/statsd/src/metrics/ValueMetricProducer.cpp
index 833f55f..f4620dd 100644
--- a/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/statsd/src/metrics/ValueMetricProducer.cpp
@@ -54,6 +54,7 @@
 const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
 const int FIELD_ID_IS_ACTIVE = 14;
 const int FIELD_ID_DIMENSION_GUARDRAIL_HIT = 17;
+const int FIELD_ID_ESTIMATED_MEMORY_BYTES = 18;
 // for *MetricDataWrapper
 const int FIELD_ID_DATA = 1;
 const int FIELD_ID_SKIPPED = 2;
@@ -72,16 +73,18 @@
 
 template <typename AggregatedValue, typename DimExtras>
 ValueMetricProducer<AggregatedValue, DimExtras>::ValueMetricProducer(
-        const int64_t& metricId, const ConfigKey& key, const uint64_t protoHash,
+        const int64_t metricId, const ConfigKey& key, const uint64_t protoHash,
         const PullOptions& pullOptions, const BucketOptions& bucketOptions,
         const WhatOptions& whatOptions, const ConditionOptions& conditionOptions,
         const StateOptions& stateOptions, const ActivationOptions& activationOptions,
-        const GuardrailOptions& guardrailOptions)
+        const GuardrailOptions& guardrailOptions,
+        const wp<ConfigMetadataProvider> configMetadataProvider)
     : MetricProducer(metricId, key, bucketOptions.timeBaseNs, conditionOptions.conditionIndex,
                      conditionOptions.initialConditionCache, conditionOptions.conditionWizard,
                      protoHash, activationOptions.eventActivationMap,
                      activationOptions.eventDeactivationMap, stateOptions.slicedStateAtoms,
-                     stateOptions.stateGroupMap, bucketOptions.splitBucketForAppUpgrade),
+                     stateOptions.stateGroupMap, bucketOptions.splitBucketForAppUpgrade,
+                     configMetadataProvider),
       mWhatMatcherIndex(whatOptions.whatMatcherIndex),
       mEventMatcherWizard(whatOptions.matcherWizard),
       mPullerManager(pullOptions.pullerManager),
@@ -154,7 +157,8 @@
 
 template <typename AggregatedValue, typename DimExtras>
 void ValueMetricProducer<AggregatedValue, DimExtras>::onStatsdInitCompleted(
-        const int64_t& eventTimeNs) {
+        const int64_t eventTimeNs) {
+    ATRACE_CALL();
     lock_guard<mutex> lock(mMutex);
 
     if (isPulled() && mCondition == ConditionState::kTrue && mIsActive) {
@@ -225,7 +229,7 @@
 void ValueMetricProducer<AggregatedValue, DimExtras>::onStateChanged(
         int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey,
         const FieldValue& oldState, const FieldValue& newState) {
-    // TODO(b/189353769): Acquire lock.
+    std::lock_guard<std::mutex> lock(mMutex);
     VLOG("ValueMetricProducer %lld onStateChanged time %lld, State %d, key %s, %d -> %d",
          (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
          oldState.mValue.int_value, newState.mValue.int_value);
@@ -320,6 +324,8 @@
 
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ID, (long long)mMetricId);
     protoOutput->write(FIELD_TYPE_BOOL | FIELD_ID_IS_ACTIVE, isActiveLocked());
+    protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_ESTIMATED_MEMORY_BYTES,
+                       (long long)byteSizeLocked());
     if (mPastBuckets.empty() && mSkippedBuckets.empty()) {
         return;
     }
@@ -616,7 +622,7 @@
 
 template <typename AggregatedValue, typename DimExtras>
 bool ValueMetricProducer<AggregatedValue, DimExtras>::hitGuardRailLocked(
-        const MetricDimensionKey& newKey) {
+        const MetricDimensionKey& newKey) const {
     // ===========GuardRail==============
     // 1. Report the tuple count if the tuple count > soft limit
     if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) {
@@ -717,7 +723,7 @@
 // if mCondition and mIsActive are true!
 template <typename AggregatedValue, typename DimExtras>
 void ValueMetricProducer<AggregatedValue, DimExtras>::flushIfNeededLocked(
-        const int64_t& eventTimeNs) {
+        const int64_t eventTimeNs) {
     const int64_t currentBucketEndTimeNs = getCurrentBucketEndTimeNs();
     if (eventTimeNs < currentBucketEndTimeNs) {
         VLOG("eventTime is %lld, less than current bucket end time %lld", (long long)eventTimeNs,
@@ -742,7 +748,7 @@
 
 template <typename AggregatedValue, typename DimExtras>
 void ValueMetricProducer<AggregatedValue, DimExtras>::flushCurrentBucketLocked(
-        const int64_t& eventTimeNs, const int64_t& nextBucketStartTimeNs) {
+        const int64_t eventTimeNs, const int64_t nextBucketStartTimeNs) {
     if (mCondition == ConditionState::kUnknown) {
         StatsdStats::getInstance().noteBucketUnknownCondition(mMetricId);
         invalidateCurrentBucket(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN);
diff --git a/statsd/src/metrics/ValueMetricProducer.h b/statsd/src/metrics/ValueMetricProducer.h
index 6592b3e..bfde6a0 100644
--- a/statsd/src/metrics/ValueMetricProducer.h
+++ b/statsd/src/metrics/ValueMetricProducer.h
@@ -88,6 +88,7 @@
         const sp<EventMatcherWizard>& matcherWizard;
         const FieldMatcher& dimensionsInWhat;
         const vector<Matcher>& fieldMatchers;
+        const vector<ValueMetric::AggregationType> aggregationTypes;
     };
 
     struct ConditionOptions {
@@ -127,18 +128,19 @@
     }
 
     // ValueMetric needs special logic if it's a pulled atom.
-    void onStatsdInitCompleted(const int64_t& eventTimeNs) override;
+    void onStatsdInitCompleted(int64_t eventTimeNs) override;
 
     void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey,
                         const FieldValue& oldState, const FieldValue& newState) override;
 
 protected:
-    ValueMetricProducer(const int64_t& metricId, const ConfigKey& key, const uint64_t protoHash,
+    ValueMetricProducer(int64_t metricId, const ConfigKey& key, uint64_t protoHash,
                         const PullOptions& pullOptions, const BucketOptions& bucketOptions,
                         const WhatOptions& whatOptions, const ConditionOptions& conditionOptions,
                         const StateOptions& stateOptions,
                         const ActivationOptions& activationOptions,
-                        const GuardrailOptions& guardrailOptions);
+                        const GuardrailOptions& guardrailOptions,
+                        const wp<ConfigMetadataProvider> configMetadataProvider);
 
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -178,7 +180,7 @@
     }
 
     // ValueMetricProducer internal interface to handle condition change.
-    void onConditionChangedLocked(const bool condition, const int64_t eventTimeNs) override;
+    void onConditionChangedLocked(const bool condition, int64_t eventTimeNs) override;
 
     // Only called when mIsActive, the event is NOT too late, and after pulling.
     virtual void onConditionChangedInternalLocked(const ConditionState oldCondition,
@@ -187,7 +189,7 @@
     }
 
     // Internal interface to handle sliced condition change.
-    void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
+    void onSlicedConditionMayChangeLocked(bool overallCondition, int64_t eventTime) override;
 
     void dumpStatesLocked(int out, bool verbose) const override;
 
@@ -195,12 +197,11 @@
 
     // For pulled metrics, this method should only be called if a pull has been done. Else we will
     // not have complete data for the bucket.
-    void flushIfNeededLocked(const int64_t& eventTime) override;
+    void flushIfNeededLocked(int64_t eventTime) override;
 
     // For pulled metrics, this method should only be called if a pulled has been done. Else we will
     // not have complete data for the bucket.
-    void flushCurrentBucketLocked(const int64_t& eventTimeNs,
-                                  const int64_t& nextBucketStartTimeNs) override;
+    void flushCurrentBucketLocked(int64_t eventTimeNs, int64_t nextBucketStartTimeNs) override;
 
     void dropDataLocked(const int64_t dropTimeNs) override;
 
@@ -216,7 +217,7 @@
     void skipCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason);
 
     optional<InvalidConfigReason> onConfigUpdatedLocked(
-            const StatsdConfig& config, const int configIndex, const int metricIndex,
+            const StatsdConfig& config, int configIndex, int metricIndex,
             const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
             const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
             const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -320,7 +321,7 @@
     const int64_t mMinBucketSizeNs;
 
     // Util function to check whether the specified dimension hits the guardrail.
-    bool hitGuardRailLocked(const MetricDimensionKey& newKey);
+    bool hitGuardRailLocked(const MetricDimensionKey& newKey) const;
 
     bool hasReachedGuardRailLimit() const;
 
@@ -332,7 +333,7 @@
     virtual PastBucket<AggregatedValue> buildPartialBucket(int64_t bucketEndTime,
                                                            std::vector<Interval>& intervals) = 0;
 
-    virtual void closeCurrentBucket(const int64_t eventTimeNs, const int64_t nextBucketStartTimeNs);
+    virtual void closeCurrentBucket(const int64_t eventTimeNs, int64_t nextBucketStartTimeNs);
 
     virtual void initNextSlicedBucket(int64_t nextBucketStartTimeNs);
 
diff --git a/statsd/src/metrics/duration_helper/DurationTracker.h b/statsd/src/metrics/duration_helper/DurationTracker.h
index 0aa5081..a6cbfff 100644
--- a/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -73,8 +73,8 @@
 
 class DurationTracker {
 public:
-    DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-                    sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
+    DurationTracker(const ConfigKey& key, const int64_t id, const MetricDimensionKey& eventKey,
+                    const sp<ConditionWizard>& wizard, int conditionIndex, bool nesting,
                     int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs,
                     int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
                     const std::vector<sp<AnomalyTracker>>& anomalyTrackers)
@@ -95,21 +95,21 @@
 
     virtual ~DurationTracker(){};
 
-    void onConfigUpdated(const sp<ConditionWizard>& wizard, const int conditionTrackerIndex) {
+    void onConfigUpdated(const sp<ConditionWizard>& wizard, int conditionTrackerIndex) {
         sp<ConditionWizard> tmpWizard = mWizard;
         mWizard = wizard;
         mConditionTrackerIndex = conditionTrackerIndex;
         mAnomalyTrackers.clear();
     };
 
-    virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
-                           const ConditionKey& conditionKey) = 0;
-    virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
+    virtual void noteStart(const HashableDimensionKey& key, bool condition, int64_t eventTime,
+                           const ConditionKey& conditionKey, size_t dimensionHardLimit) = 0;
+    virtual void noteStop(const HashableDimensionKey& key, int64_t eventTime,
                           const bool stopAll) = 0;
     virtual void noteStopAll(const int64_t eventTime) = 0;
 
     virtual void onSlicedConditionMayChange(const int64_t timestamp) = 0;
-    virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0;
+    virtual void onConditionChanged(bool condition, int64_t timestamp) = 0;
 
     virtual void onStateChanged(const int64_t timestamp, const int32_t atomId,
                                 const FieldValue& newState) = 0;
@@ -123,7 +123,7 @@
     // Should only be called during an app upgrade or from this tracker's flushIfNeeded. If from
     // an app upgrade, we assume that we're trying to form a partial bucket.
     virtual bool flushCurrentBucket(
-            const int64_t& eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
+            int64_t eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
             const int64_t globalConditionTrueNs,
             std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) = 0;
 
@@ -138,7 +138,7 @@
     virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0;
 
     // Replace old value with new value for the given state atom.
-    virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0;
+    virtual void updateCurrentStateKey(int32_t atomId, const FieldValue& newState) = 0;
 
     virtual bool hasAccumulatedDuration() const = 0;
 
@@ -162,6 +162,8 @@
 protected:
     virtual bool hasStartedDuration() const = 0;
 
+    // Convenience to compute the current bucket's end time, which is always aligned with the
+    // start time of the metric.
     int64_t getCurrentBucketEndTimeNs() const {
         return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
     }
@@ -188,8 +190,8 @@
         }
     }
 
-    void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey,
-                                        const int64_t& bucketValue, const int64_t& bucketNum) {
+    void addPastBucketToAnomalyTrackers(const MetricDimensionKey& eventKey, int64_t bucketValue,
+                                        int64_t bucketNum) {
         for (auto& anomalyTracker : mAnomalyTrackers) {
             if (anomalyTracker != nullptr) {
                 anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum);
@@ -197,8 +199,8 @@
         }
     }
 
-    void detectAndDeclareAnomaly(const int64_t& timestamp, const int64_t& currBucketNum,
-                                 const int64_t& currentBucketValue) {
+    void detectAndDeclareAnomaly(int64_t timestamp, int64_t currBucketNum,
+                                 int64_t currentBucketValue) {
         for (auto& anomalyTracker : mAnomalyTrackers) {
             if (anomalyTracker != nullptr) {
                 anomalyTracker->detectAndDeclareAnomaly(timestamp, currBucketNum, mTrackerId,
@@ -207,12 +209,6 @@
         }
     }
 
-    // Convenience to compute the current bucket's end time, which is always aligned with the
-    // start time of the metric.
-    int64_t getCurrentBucketEndTimeNs() {
-        return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
-    }
-
     void setEventKey(const MetricDimensionKey& eventKey) {
         mEventKey = eventKey;
     }
@@ -272,12 +268,16 @@
 
     std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
 
-    bool mHasHitGuardrail;
+    mutable bool mHasHitGuardrail;
 
     FRIEND_TEST(OringDurationTrackerTest, TestPredictAnomalyTimestamp);
     FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionExpiredAlarm);
     FRIEND_TEST(OringDurationTrackerTest, TestAnomalyDetectionFiredAlarm);
 
+    FRIEND_TEST(OringDurationTrackerTest_DimLimit, TestDimLimit);
+
+    FRIEND_TEST(MaxDurationTrackerTest_DimLimit, TestDimLimit);
+
     FRIEND_TEST(ConfigUpdateTest, TestUpdateDurationMetrics);
     FRIEND_TEST(ConfigUpdateTest, TestUpdateAlerts);
 };
diff --git a/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index ea99ae8..8a253e2 100644
--- a/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -24,12 +24,12 @@
 namespace os {
 namespace statsd {
 
-MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
+MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t id,
                                        const MetricDimensionKey& eventKey,
-                                       sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
-                                       int64_t currentBucketStartNs, int64_t currentBucketNum,
-                                       int64_t startTimeNs, int64_t bucketSizeNs,
-                                       bool conditionSliced, bool fullLink,
+                                       const sp<ConditionWizard>& wizard, int conditionIndex,
+                                       bool nesting, int64_t currentBucketStartNs,
+                                       int64_t currentBucketNum, int64_t startTimeNs,
+                                       int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
                                        const vector<sp<AnomalyTracker>>& anomalyTrackers)
     : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
                       currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
@@ -37,18 +37,19 @@
     mDuration = 0;
 }
 
-bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
+bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey,
+                                      size_t dimensionHardLimit) const {
     // ===========GuardRail==============
     if (mInfos.find(newKey) != mInfos.end()) {
         // if the key existed, we are good!
         return false;
     }
     // 1. Report the tuple count if the tuple count > soft limit
-    if (mInfos.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+    if (mInfos.size() >= StatsdStats::kDimensionKeySizeSoftLimit) {
         size_t newTupleCount = mInfos.size() + 1;
         StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mTrackerId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
-        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+        if (newTupleCount > dimensionHardLimit) {
             if (!mHasHitGuardrail) {
                 ALOGE("MaxDurTracker %lld dropping data for dimension key %s",
                       (long long)mTrackerId, newKey.toString().c_str());
@@ -62,9 +63,10 @@
 }
 
 void MaxDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
-                                   const int64_t eventTime, const ConditionKey& conditionKey) {
+                                   const int64_t eventTime, const ConditionKey& conditionKey,
+                                   size_t dimensionHardLimit) {
     // this will construct a new DurationInfo if this key didn't exist.
-    if (hitGuardRail(key)) {
+    if (hitGuardRail(key, dimensionHardLimit)) {
         return;
     }
 
@@ -173,7 +175,7 @@
 }
 
 bool MaxDurationTracker::flushCurrentBucket(
-        const int64_t& eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
+        const int64_t eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
         const int64_t globalConditionTrueNs,
         std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) {
     VLOG("MaxDurationTracker flushing.....");
diff --git a/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index 8c18e64..2769232 100644
--- a/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -28,30 +28,29 @@
 // they stop or bucket expires.
 class MaxDurationTracker : public DurationTracker {
 public:
-    MaxDurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-                       sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
+    MaxDurationTracker(const ConfigKey& key, const int64_t id, const MetricDimensionKey& eventKey,
+                       const sp<ConditionWizard>& wizard, int conditionIndex, bool nesting,
                        int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs,
                        int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
                        const std::vector<sp<AnomalyTracker>>& anomalyTrackers);
 
     MaxDurationTracker(const MaxDurationTracker& tracker) = default;
 
-    void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
-                   const ConditionKey& conditionKey) override;
-    void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
-                  const bool stopAll) override;
+    void noteStart(const HashableDimensionKey& key, bool condition, int64_t eventTime,
+                   const ConditionKey& conditionKey, size_t dimensionHardLimit) override;
+    void noteStop(const HashableDimensionKey& key, int64_t eventTime, const bool stopAll) override;
     void noteStopAll(const int64_t eventTime) override;
 
     bool flushIfNeeded(
             int64_t timestampNs, const optional<UploadThreshold>& uploadThreshold,
             std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
     bool flushCurrentBucket(
-            const int64_t& eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
+            int64_t eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
             const int64_t globalConditionTrueNs,
             std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>*) override;
 
     void onSlicedConditionMayChange(const int64_t timestamp) override;
-    void onConditionChanged(bool condition, const int64_t timestamp) override;
+    void onConditionChanged(bool condition, int64_t timestamp) override;
 
     void onStateChanged(const int64_t timestamp, const int32_t atomId,
                         const FieldValue& newState) override;
@@ -64,7 +63,7 @@
 
     int64_t getCurrentStateKeyFullBucketDuration() const override;
 
-    void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+    void updateCurrentStateKey(int32_t atomId, const FieldValue& newState);
 
     bool hasAccumulatedDuration() const override;
 
@@ -81,7 +80,7 @@
                               const int64_t timestamp);
 
     // return true if we should not allow newKey to be tracked because we are above the threshold
-    bool hitGuardRail(const HashableDimensionKey& newKey);
+    bool hitGuardRail(const HashableDimensionKey& newKey, size_t dimensionHardLimit) const;
 
     FRIEND_TEST(MaxDurationTrackerTest, TestSimpleMaxDuration);
     FRIEND_TEST(MaxDurationTrackerTest, TestCrossBucketBoundary);
diff --git a/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 9437dbe..3fab0e8 100644
--- a/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -24,11 +24,14 @@
 
 using std::pair;
 
-OringDurationTracker::OringDurationTracker(
-        const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-        sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs,
-        int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
-        bool fullLink, const vector<sp<AnomalyTracker>>& anomalyTrackers)
+OringDurationTracker::OringDurationTracker(const ConfigKey& key, const int64_t id,
+                                           const MetricDimensionKey& eventKey,
+                                           const sp<ConditionWizard>& wizard, int conditionIndex,
+                                           bool nesting, int64_t currentBucketStartNs,
+                                           int64_t currentBucketNum, int64_t startTimeNs,
+                                           int64_t bucketSizeNs, bool conditionSliced,
+                                           bool fullLink,
+                                           const vector<sp<AnomalyTracker>>& anomalyTrackers)
     : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
                       currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
                       anomalyTrackers),
@@ -37,17 +40,18 @@
     mLastStartTime = 0;
 }
 
-bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
+bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey,
+                                        size_t dimensionHardLimit) const {
     // ===========GuardRail==============
     // 1. Report the tuple count if the tuple count > soft limit
     if (mConditionKeyMap.find(newKey) != mConditionKeyMap.end()) {
         return false;
     }
-    if (mConditionKeyMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
+    if (mConditionKeyMap.size() >= StatsdStats::kDimensionKeySizeSoftLimit) {
         size_t newTupleCount = mConditionKeyMap.size() + 1;
         StatsdStats::getInstance().noteMetricDimensionSize(mConfigKey, mTrackerId, newTupleCount);
         // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
-        if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
+        if (newTupleCount > dimensionHardLimit) {
             if (!mHasHitGuardrail) {
                 ALOGE("OringDurTracker %lld dropping data for dimension key %s",
                       (long long)mTrackerId, newKey.toString().c_str());
@@ -61,8 +65,9 @@
 }
 
 void OringDurationTracker::noteStart(const HashableDimensionKey& key, bool condition,
-                                     const int64_t eventTime, const ConditionKey& conditionKey) {
-    if (hitGuardRail(key)) {
+                                     const int64_t eventTime, const ConditionKey& conditionKey,
+                                     size_t dimensionHardLimit) {
+    if (hitGuardRail(key, dimensionHardLimit)) {
         return;
     }
     if (condition) {
@@ -136,7 +141,7 @@
 }
 
 bool OringDurationTracker::flushCurrentBucket(
-        const int64_t& eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
+        const int64_t eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
         const int64_t globalConditionTrueNs,
         std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) {
     VLOG("OringDurationTracker Flushing.............");
diff --git a/statsd/src/metrics/duration_helper/OringDurationTracker.h b/statsd/src/metrics/duration_helper/OringDurationTracker.h
index c6fa425..a50862b 100644
--- a/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -26,29 +26,27 @@
 // Tracks the "Or'd" duration -- if 2 durations are overlapping, they won't be double counted.
 class OringDurationTracker : public DurationTracker {
 public:
-    OringDurationTracker(const ConfigKey& key, const int64_t& id,
-                         const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
-                         int conditionIndex, bool nesting, int64_t currentBucketStartNs,
-                         int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs,
-                         bool conditionSliced, bool fullLink,
-                         const std::vector<sp<AnomalyTracker>>& anomalyTrackers);
+    OringDurationTracker(const ConfigKey& key, const int64_t id, const MetricDimensionKey& eventKey,
+                         const sp<ConditionWizard>& wizard, int conditionIndex, bool nesting,
+                         int64_t currentBucketStartNs, int64_t currentBucketNum,
+                         int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
+                         bool fullLink, const std::vector<sp<AnomalyTracker>>& anomalyTrackers);
 
     OringDurationTracker(const OringDurationTracker& tracker) = default;
 
-    void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
-                   const ConditionKey& conditionKey) override;
-    void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
-                  const bool stopAll) override;
+    void noteStart(const HashableDimensionKey& key, bool condition, int64_t eventTime,
+                   const ConditionKey& conditionKey, size_t dimensionHardLimit) override;
+    void noteStop(const HashableDimensionKey& key, int64_t eventTime, const bool stopAll) override;
     void noteStopAll(const int64_t eventTime) override;
 
     void onSlicedConditionMayChange(const int64_t timestamp) override;
-    void onConditionChanged(bool condition, const int64_t timestamp) override;
+    void onConditionChanged(bool condition, int64_t timestamp) override;
 
     void onStateChanged(const int64_t timestamp, const int32_t atomId,
                         const FieldValue& newState) override;
 
     bool flushCurrentBucket(
-            const int64_t& eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
+            int64_t eventTimeNs, const optional<UploadThreshold>& uploadThreshold,
             const int64_t globalConditionTrueNs,
             std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
     bool flushIfNeeded(
@@ -63,7 +61,7 @@
 
     int64_t getCurrentStateKeyFullBucketDuration() const override;
 
-    void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+    void updateCurrentStateKey(int32_t atomId, const FieldValue& newState);
 
     bool hasAccumulatedDuration() const override;
 
@@ -83,7 +81,7 @@
     std::unordered_map<HashableDimensionKey, ConditionKey> mConditionKeyMap;
 
     // return true if we should not allow newKey to be tracked because we are above the threshold
-    bool hitGuardRail(const HashableDimensionKey& newKey);
+    bool hitGuardRail(const HashableDimensionKey& newKey, size_t dimensionHardLimit) const;
 
     FRIEND_TEST(OringDurationTrackerTest, TestDurationOverlap);
     FRIEND_TEST(OringDurationTrackerTest, TestCrossBucketBoundary);
diff --git a/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index c3972d9..a296dad 100644
--- a/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -37,7 +37,7 @@
         const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
         const vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
         const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
-        vector<UpdateStatus>& matchersToUpdate, vector<bool>& cycleTracker) {
+        vector<UpdateStatus>& matchersToUpdate, vector<uint8_t>& cycleTracker) {
     // Have already examined this matcher.
     if (matchersToUpdate[matcherIdx] != UPDATE_UNKNOWN) {
         return nullopt;
@@ -145,7 +145,7 @@
 
     // For combination matchers, we need to determine if any children need to be updated.
     vector<UpdateStatus> matchersToUpdate(atomMatcherCount, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(atomMatcherCount, false);
+    vector<uint8_t> cycleTracker(atomMatcherCount, false);
     for (int i = 0; i < atomMatcherCount; i++) {
         invalidConfigReason = determineMatcherUpdateStatus(
                 config, i, oldAtomMatchingTrackerMap, oldAtomMatchingTrackers,
@@ -171,7 +171,7 @@
                 const sp<AtomMatchingTracker>& tracker =
                         oldAtomMatchingTrackers[oldAtomMatchingTrackerIt->second];
                 invalidConfigReason =
-                        tracker->onConfigUpdated(matcherProtos[i], i, newAtomMatchingTrackerMap);
+                        tracker->onConfigUpdated(matcherProtos[i], newAtomMatchingTrackerMap);
                 if (invalidConfigReason.has_value()) {
                     ALOGW("Config update failed for matcher %lld", (long long)id);
                     return invalidConfigReason;
@@ -184,7 +184,7 @@
                 [[fallthrough]];  // Intentionally fallthrough to create the new matcher.
             case UPDATE_NEW: {
                 sp<AtomMatchingTracker> tracker =
-                        createAtomMatchingTracker(matcher, i, uidMap, invalidConfigReason);
+                        createAtomMatchingTracker(matcher, uidMap, invalidConfigReason);
                 if (tracker == nullptr) {
                     return invalidConfigReason;
                 }
@@ -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(matcherProtos, newAtomMatchingTrackers,
-                                            newAtomMatchingTrackerMap, cycleTracker);
+        const auto [invalidConfigReason, _] =
+                matcher->init(matcherIndex, matcherProtos, newAtomMatchingTrackers,
+                              newAtomMatchingTrackerMap, cycleTracker);
         if (invalidConfigReason.has_value()) {
             return invalidConfigReason;
         }
@@ -238,7 +239,7 @@
         const vector<sp<ConditionTracker>>& oldConditionTrackers,
         const unordered_map<int64_t, int>& newConditionTrackerMap,
         const set<int64_t>& replacedMatchers, vector<UpdateStatus>& conditionsToUpdate,
-        vector<bool>& cycleTracker) {
+        vector<uint8_t>& cycleTracker) {
     // Have already examined this condition.
     if (conditionsToUpdate[conditionIdx] != UPDATE_UNKNOWN) {
         return nullopt;
@@ -369,7 +370,7 @@
     }
 
     vector<UpdateStatus> conditionsToUpdate(conditionTrackerCount, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(conditionTrackerCount, false);
+    vector<uint8_t> cycleTracker(conditionTrackerCount, false);
     for (int i = 0; i < conditionTrackerCount; i++) {
         invalidConfigReason = determineConditionUpdateStatus(
                 config, i, oldConditionTrackerMap, oldConditionTrackers, newConditionTrackerMap,
@@ -519,7 +520,7 @@
     if (invalidConfigReason.has_value()) {
         return invalidConfigReason;
     }
-    const sp<MetricProducer> oldMetricProducer = oldMetricProducers[oldMetricProducerIt->second];
+    const sp<MetricProducer>& oldMetricProducer = oldMetricProducers[oldMetricProducerIt->second];
     if (oldMetricProducer->getMetricType() != metricType ||
         oldMetricProducer->getProtoHash() != metricHash) {
         updateStatus = UPDATE_REPLACE;
@@ -729,6 +730,7 @@
         const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
         const set<int64_t>& replacedStates, const unordered_map<int64_t, int>& oldMetricProducerMap,
         const vector<sp<MetricProducer>>& oldMetricProducers,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         unordered_map<int64_t, int>& newMetricProducerMap,
         vector<sp<MetricProducer>>& newMetricProducers,
         unordered_map<int, vector<int>>& conditionToMetricMap,
@@ -744,6 +746,12 @@
     newMetricProducers.reserve(allMetricsCount);
     optional<InvalidConfigReason> invalidConfigReason;
 
+    if (config.has_restricted_metrics_delegate_package_name() &&
+        allMetricsCount != config.event_metric_size()) {
+        ALOGE("Restricted metrics only support event metric");
+        return InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED);
+    }
+
     // Construct map from metric id to metric activation index. The map will be used to determine
     // the metric activation corresponding to a metric.
     unordered_map<int64_t, int> metricToActivationMap;
@@ -795,7 +803,7 @@
                         allStateGroupMaps, metricToActivationMap, trackerToMetricMap,
                         conditionToMetricMap, activationAtomTrackerToMetricMap,
                         deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                        invalidConfigReason);
+                        invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -837,7 +845,7 @@
                         allStateGroupMaps, metricToActivationMap, trackerToMetricMap,
                         conditionToMetricMap, activationAtomTrackerToMetricMap,
                         deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                        invalidConfigReason);
+                        invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -878,7 +886,7 @@
                         initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap,
                         conditionToMetricMap, activationAtomTrackerToMetricMap,
                         deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                        invalidConfigReason);
+                        invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -921,7 +929,7 @@
                         stateAtomIdMap, allStateGroupMaps, metricToActivationMap,
                         trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
                         deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                        invalidConfigReason);
+                        invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -963,7 +971,7 @@
                         conditionTrackerMap, initialConditionCache, wizard, matcherWizard,
                         metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                         activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                        metricsWithActivation, invalidConfigReason);
+                        metricsWithActivation, invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -1007,7 +1015,7 @@
                         stateAtomIdMap, allStateGroupMaps, metricToActivationMap,
                         trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
                         deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                        invalidConfigReason);
+                        invalidConfigReason, configMetadataProvider);
                 break;
             }
             default: {
@@ -1058,6 +1066,15 @@
             newMetricProducers[i]->prepareFirstBucket();
         }
     }
+
+    for (const sp<MetricProducer>& oldMetricProducer : oldMetricProducers) {
+        const auto& it = newMetricProducerMap.find(oldMetricProducer->getMetricId());
+        // Consider metric removed if it's not present in newMetricProducerMap or it's replaced.
+        if (it == newMetricProducerMap.end() ||
+            replacedMetrics.find(oldMetricProducer->getMetricId()) != replacedMetrics.end()) {
+            oldMetricProducer->onMetricRemove();
+        }
+    }
     return nullopt;
 }
 
@@ -1191,6 +1208,7 @@
         const vector<sp<AnomalyTracker>>& oldAnomalyTrackers,
         const unordered_map<int64_t, int>& oldAlertTrackerMap,
         const map<int64_t, uint64_t>& oldStateProtoHashes,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         std::unordered_map<int, std::vector<int>>& allTagIdsToMatchersMap,
         vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
         unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -1252,9 +1270,10 @@
             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
             newConditionTrackerMap, replacedConditions, newConditionTrackers, conditionCache,
             stateAtomIdMap, allStateGroupMaps, replacedStates, oldMetricProducerMap,
-            oldMetricProducers, newMetricProducerMap, newMetricProducers, conditionToMetricMap,
-            trackerToMetricMap, noReportMetricIds, activationTrackerToMetricMap,
-            deactivationTrackerToMetricMap, metricsWithActivation, replacedMetrics);
+            oldMetricProducers, configMetadataProvider, newMetricProducerMap, newMetricProducers,
+            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+            activationTrackerToMetricMap, deactivationTrackerToMetricMap, metricsWithActivation,
+            replacedMetrics);
     if (invalidConfigReason.has_value()) {
         ALOGE("updateMetrics failed");
         return invalidConfigReason;
diff --git a/statsd/src/metrics/parsing_utils/config_update_utils.h b/statsd/src/metrics/parsing_utils/config_update_utils.h
index 3bd1e80..a8915c2 100644
--- a/statsd/src/metrics/parsing_utils/config_update_utils.h
+++ b/statsd/src/metrics/parsing_utils/config_update_utils.h
@@ -21,6 +21,7 @@
 #include "anomaly/AlarmMonitor.h"
 #include "anomaly/AlarmTracker.h"
 #include "condition/ConditionTracker.h"
+#include "config/ConfigMetadataProvider.h"
 #include "external/StatsPullerManager.h"
 #include "matchers/AtomMatchingTracker.h"
 #include "metrics/MetricProducer.h"
@@ -46,11 +47,11 @@
 // [cycleTracker]: intermediate param used during recursion.
 // Returns nullopt if successful and InvalidConfigReason if not.
 optional<InvalidConfigReason> determineMatcherUpdateStatus(
-        const StatsdConfig& config, const int matcherIdx,
+        const StatsdConfig& config, int matcherIdx,
         const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
         const std::vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
         const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
-        std::vector<UpdateStatus>& matchersToUpdate, std::vector<bool>& cycleTracker);
+        std::vector<UpdateStatus>& matchersToUpdate, std::vector<uint8_t>& cycleTracker);
 
 // Updates the AtomMatchingTrackers.
 // input:
@@ -86,12 +87,12 @@
 // [cycleTracker]: intermediate param used during recursion.
 // Returns nullopt if successful and InvalidConfigReason if not.
 optional<InvalidConfigReason> determineConditionUpdateStatus(
-        const StatsdConfig& config, const int conditionIdx,
+        const StatsdConfig& config, int conditionIdx,
         const std::unordered_map<int64_t, int>& oldConditionTrackerMap,
         const std::vector<sp<ConditionTracker>>& oldConditionTrackers,
         const std::unordered_map<int64_t, int>& newConditionTrackerMap,
         const std::set<int64_t>& replacedMatchers, std::vector<UpdateStatus>& conditionsToUpdate,
-        std::vector<bool>& cycleTracker);
+        std::vector<uint8_t>& cycleTracker);
 
 // Updates ConditionTrackers
 // input:
@@ -164,7 +165,7 @@
 // [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
 // Returns nullopt if successful and InvalidConfigReason if not.
 optional<InvalidConfigReason> updateMetrics(
-        const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+        const ConfigKey& key, const StatsdConfig& config, int64_t timeBaseNs,
         const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
         const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
         const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -179,6 +180,7 @@
         const std::set<int64_t>& replacedStates,
         const std::unordered_map<int64_t, int>& oldMetricProducerMap,
         const std::vector<sp<MetricProducer>>& oldMetricProducers,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         std::unordered_map<int64_t, int>& newMetricProducerMap,
         std::vector<sp<MetricProducer>>& newMetricProducers,
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
@@ -219,7 +221,7 @@
 // [newAnomalyTrackers]: contains the list of sp to the AnomalyTrackers created.
 // Returns nullopt if successful and InvalidConfigReason if not.
 optional<InvalidConfigReason> updateAlerts(
-        const StatsdConfig& config, const int64_t currentTimeNs,
+        const StatsdConfig& config, int64_t currentTimeNs,
         const std::unordered_map<int64_t, int>& metricProducerMap,
         const std::set<int64_t>& replacedMetrics,
         const std::unordered_map<int64_t, int>& oldAlertTrackerMap,
@@ -234,7 +236,7 @@
 optional<InvalidConfigReason> updateStatsdConfig(
         const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
         const sp<StatsPullerManager>& pullerManager, const sp<AlarmMonitor>& anomalyAlarmMonitor,
-        const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
+        const sp<AlarmMonitor>& periodicAlarmMonitor, int64_t timeBaseNs,
         const int64_t currentTimeNs,
         const std::vector<sp<AtomMatchingTracker>>& oldAtomMatchingTrackers,
         const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
@@ -245,6 +247,7 @@
         const std::vector<sp<AnomalyTracker>>& oldAnomalyTrackers,
         const std::unordered_map<int64_t, int>& oldAlertTrackerMap,
         const std::map<int64_t, uint64_t>& oldStateProtoHashes,
+        const wp<ConfigMetadataProvider> configMetadataProvider,
         std::unordered_map<int, std::vector<int>>& allTagIdsToMatchersMap,
         std::vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
         std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index be41c90..43744d3 100644
--- a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -37,6 +37,7 @@
 #include "metrics/KllMetricProducer.h"
 #include "metrics/MetricProducer.h"
 #include "metrics/NumericValueMetricProducer.h"
+#include "metrics/RestrictedEventMetricProducer.h"
 #include "state/StateManager.h"
 #include "stats_util.h"
 
@@ -63,10 +64,75 @@
     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(
-        const AtomMatcher& logMatcher, const int index, const sp<UidMap>& uidMap,
+        const AtomMatcher& logMatcher, const sp<UidMap>& uidMap,
         optional<InvalidConfigReason>& invalidConfigReason) {
     string serializedMatcher;
     if (!logMatcher.SerializeToString(&serializedMatcher)) {
@@ -78,12 +144,18 @@
     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(), index, protoHash, logMatcher.simple_atom_matcher(), uidMap);
+                    logMatcher.id(), protoHash, logMatcher.simple_atom_matcher(), uidMap);
             return simpleAtomMatcher;
         }
         case AtomMatcher::ContentsCase::kCombination:
-            return new CombinationAtomMatchingTracker(logMatcher.id(), index, protoHash);
+            return new CombinationAtomMatchingTracker(logMatcher.id(), protoHash);
         default:
             ALOGE("Matcher \"%lld\" malformed", (long long)logMatcher.id());
             invalidConfigReason = createInvalidConfigReasonWithMatcher(
@@ -248,7 +320,7 @@
     return nullopt;
 }
 
-optional<InvalidConfigReason> handleMetricWithSampling(
+optional<InvalidConfigReason> handleMetricWithDimensionalSampling(
         const int64_t metricId, const DimensionalSamplingInfo& dimSamplingInfo,
         const vector<Matcher>& dimensionsInWhat, SamplingInfo& samplingInfo) {
     if (!dimSamplingInfo.has_sampled_what_field()) {
@@ -437,7 +509,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find metric id or \"what\" in CountMetric \"%lld\"", (long long)metric.id());
         invalidConfigReason =
@@ -525,14 +598,14 @@
         return nullopt;
     }
 
-    sp<MetricProducer> metricProducer =
-            new CountMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
-                                    metricHash, timeBaseNs, currentTimeNs, eventActivationMap,
-                                    eventDeactivationMap, slicedStateAtoms, stateGroupMap);
+    sp<MetricProducer> metricProducer = new CountMetricProducer(
+            key, metric, conditionIndex, initialConditionCache, wizard, metricHash, timeBaseNs,
+            currentTimeNs, configMetadataProvider, eventActivationMap, eventDeactivationMap,
+            slicedStateAtoms, stateGroupMap);
 
     SamplingInfo samplingInfo;
     if (metric.has_dimensional_sampling_info()) {
-        invalidConfigReason = handleMetricWithSampling(
+        invalidConfigReason = handleMetricWithDimensionalSampling(
                 metric.id(), metric.dimensional_sampling_info(), dimensionsInWhat, samplingInfo);
         if (invalidConfigReason.has_value()) {
             return nullopt;
@@ -558,7 +631,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find metric id or \"what\" in DurationMetric \"%lld\"",
               (long long)metric.id());
@@ -619,7 +693,7 @@
         }
     }
 
-    FieldMatcher internalDimensions = simplePredicate.dimensions();
+    const FieldMatcher& internalDimensions = simplePredicate.dimensions();
 
     int conditionIndex = -1;
     if (metric.has_condition()) {
@@ -706,8 +780,8 @@
     sp<MetricProducer> metricProducer = new DurationMetricProducer(
             key, metric, conditionIndex, initialConditionCache, whatIndex, startIndex, stopIndex,
             stopAllIndex, nesting, wizard, metricHash, internalDimensions, timeBaseNs,
-            currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
-            stateGroupMap);
+            currentTimeNs, configMetadataProvider, eventActivationMap, eventDeactivationMap,
+            slicedStateAtoms, stateGroupMap);
     if (!metricProducer->isValid()) {
         // TODO: Remove once invalidConfigReason is added to the DurationMetricProducer constructor
         invalidConfigReason = InvalidConfigReason(
@@ -717,7 +791,7 @@
 
     SamplingInfo samplingInfo;
     if (metric.has_dimensional_sampling_info()) {
-        invalidConfigReason = handleMetricWithSampling(
+        invalidConfigReason = handleMetricWithDimensionalSampling(
                 metric.id(), metric.dimensional_sampling_info(), dimensionsInWhat, samplingInfo);
         if (invalidConfigReason.has_value()) {
             return nullopt;
@@ -741,7 +815,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find the metric name or what in config");
         invalidConfigReason =
@@ -773,6 +848,12 @@
         }
     }
 
+    if (metric.sampling_percentage() < 1 || metric.sampling_percentage() > 100) {
+        invalidConfigReason = InvalidConfigReason(
+                INVALID_CONFIG_REASON_METRIC_INCORRECT_SAMPLING_PERCENTAGE, metric.id());
+        return nullopt;
+    }
+
     unordered_map<int, shared_ptr<Activation>> eventActivationMap;
     unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
     invalidConfigReason = handleMetricActivation(
@@ -788,9 +869,14 @@
         return nullopt;
     }
 
+    if (config.has_restricted_metrics_delegate_package_name()) {
+        return {new RestrictedEventMetricProducer(
+                key, metric, conditionIndex, initialConditionCache, wizard, metricHash, timeBaseNs,
+                configMetadataProvider, eventActivationMap, eventDeactivationMap)};
+    }
     return {new EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
-                                    metricHash, timeBaseNs, eventActivationMap,
-                                    eventDeactivationMap)};
+                                    metricHash, timeBaseNs, configMetadataProvider,
+                                    eventActivationMap, eventDeactivationMap)};
 }
 
 optional<sp<MetricProducer>> createNumericValueMetricProducerAndUpdateMetadata(
@@ -810,7 +896,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find metric id or \"what\" in ValueMetric \"%lld\"", (long long)metric.id());
         invalidConfigReason =
@@ -839,6 +926,27 @@
         return nullopt;
     }
 
+    std::vector<ValueMetric::AggregationType> aggregationTypes;
+    if (metric.aggregation_types_size() != 0) {
+        if (metric.has_aggregation_type()) {
+            invalidConfigReason = InvalidConfigReason(
+                    INVALID_CONFIG_REASON_VALUE_METRIC_DEFINES_SINGLE_AND_MULTIPLE_AGG_TYPES,
+                    metric.id());
+            return nullopt;
+        }
+        if (metric.aggregation_types_size() != (int)fieldMatchers.size()) {
+            invalidConfigReason = InvalidConfigReason(
+                    INVALID_CONFIG_REASON_VALUE_METRIC_AGG_TYPES_DNE_VALUE_FIELDS_SIZE,
+                    metric.id());
+            return nullopt;
+        }
+        for (int i = 0; i < metric.aggregation_types_size(); i++) {
+            aggregationTypes.push_back(metric.aggregation_types(i));
+        }
+    } else {  // aggregation_type() is set or default is used.
+        aggregationTypes.push_back(metric.aggregation_type());
+    }
+
     int trackerIndex;
     invalidConfigReason = handleMetricWithAtomMatchingTrackers(
             metric.what(), metric.id(), metricIndex,
@@ -848,7 +956,7 @@
         return nullopt;
     }
 
-    sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
+    const sp<AtomMatchingTracker>& atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
     int atomTagId = *(atomMatcher->getAtomIds().begin());
     int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1;
 
@@ -921,7 +1029,9 @@
     const bool shouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what());
 
     const auto [dimensionSoftLimit, dimensionHardLimit] =
-            StatsdStats::getAtomDimensionKeySizeLimits(pullTagId);
+            StatsdStats::getAtomDimensionKeySizeLimits(
+                    pullTagId,
+                    StatsdStats::clampDimensionKeySizeLimit(metric.max_dimensions_per_bucket()));
 
     // get the condition_correction_threshold_nanos value
     const optional<int64_t> conditionCorrectionThresholdNs =
@@ -934,14 +1044,15 @@
             {timeBaseNs, currentTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
              conditionCorrectionThresholdNs, getAppUpgradeBucketSplit(metric)},
             {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions, trackerIndex,
-             matcherWizard, metric.dimensions_in_what(), fieldMatchers},
+             matcherWizard, metric.dimensions_in_what(), fieldMatchers, aggregationTypes},
             {conditionIndex, metric.links(), initialConditionCache, wizard},
             {metric.state_link(), slicedStateAtoms, stateGroupMap},
-            {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit});
+            {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit},
+            configMetadataProvider);
 
     SamplingInfo samplingInfo;
     if (metric.has_dimensional_sampling_info()) {
-        invalidConfigReason = handleMetricWithSampling(
+        invalidConfigReason = handleMetricWithDimensionalSampling(
                 metric.id(), metric.dimensional_sampling_info(), dimensionsInWhat, samplingInfo);
         if (invalidConfigReason.has_value()) {
             return nullopt;
@@ -969,7 +1080,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find metric id or \"what\" in KllMetric \"%lld\"", (long long)metric.id());
         invalidConfigReason =
@@ -1075,24 +1187,32 @@
     const bool containsAnyPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
     const bool shouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what());
 
-    sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
+    const sp<AtomMatchingTracker>& atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
     const int atomTagId = *(atomMatcher->getAtomIds().begin());
     const auto [dimensionSoftLimit, dimensionHardLimit] =
-            StatsdStats::getAtomDimensionKeySizeLimits(atomTagId);
+            StatsdStats::getAtomDimensionKeySizeLimits(
+                    atomTagId,
+                    StatsdStats::clampDimensionKeySizeLimit(metric.max_dimensions_per_bucket()));
 
     sp<MetricProducer> metricProducer = new KllMetricProducer(
             key, metric, metricHash, {/*pullTagId=*/-1, pullerManager},
             {timeBaseNs, currentTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
              /*conditionCorrectionThresholdNs=*/nullopt, getAppUpgradeBucketSplit(metric)},
-            {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions, trackerIndex,
-             matcherWizard, metric.dimensions_in_what(), fieldMatchers},
+            {containsAnyPositionInDimensionsInWhat,
+             shouldUseNestedDimensions,
+             trackerIndex,
+             matcherWizard,
+             metric.dimensions_in_what(),
+             fieldMatchers,
+             {}},
             {conditionIndex, metric.links(), initialConditionCache, wizard},
             {metric.state_link(), slicedStateAtoms, stateGroupMap},
-            {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit});
+            {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit},
+            configMetadataProvider);
 
     SamplingInfo samplingInfo;
     if (metric.has_dimensional_sampling_info()) {
-        invalidConfigReason = handleMetricWithSampling(
+        invalidConfigReason = handleMetricWithDimensionalSampling(
                 metric.id(), metric.dimensional_sampling_info(), dimensionsInWhat, samplingInfo);
         if (invalidConfigReason.has_value()) {
             return nullopt;
@@ -1118,7 +1238,8 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason) {
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     if (!metric.has_id() || !metric.has_what()) {
         ALOGE("cannot find metric id or \"what\" in GaugeMetric \"%lld\"", (long long)metric.id());
         invalidConfigReason =
@@ -1151,7 +1272,7 @@
         return nullopt;
     }
 
-    sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
+    const sp<AtomMatchingTracker>& atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
     int atomTagId = *(atomMatcher->getAtomIds().begin());
     int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1;
 
@@ -1178,7 +1299,7 @@
         if (invalidConfigReason.has_value()) {
             return nullopt;
         }
-        sp<AtomMatchingTracker> triggerAtomMatcher =
+        const sp<AtomMatchingTracker>& triggerAtomMatcher =
                 allAtomMatchingTrackers.at(triggerTrackerIndex);
         triggerAtomId = *(triggerAtomMatcher->getAtomIds().begin());
     }
@@ -1200,6 +1321,38 @@
         }
     }
 
+    if (pullTagId != -1 && metric.sampling_percentage() != 100) {
+        invalidConfigReason = InvalidConfigReason(
+                INVALID_CONFIG_REASON_GAUGE_METRIC_PULLED_WITH_SAMPLING, metric.id());
+        return nullopt;
+    }
+
+    if (metric.sampling_percentage() < 1 || metric.sampling_percentage() > 100) {
+        invalidConfigReason = InvalidConfigReason(
+                INVALID_CONFIG_REASON_METRIC_INCORRECT_SAMPLING_PERCENTAGE, metric.id());
+        return nullopt;
+    }
+
+    if (metric.pull_probability() < 1 || metric.pull_probability() > 100) {
+        invalidConfigReason = InvalidConfigReason(
+                INVALID_CONFIG_REASON_METRIC_INCORRECT_PULL_PROBABILITY, metric.id());
+        return nullopt;
+    }
+
+    if (metric.pull_probability() != 100) {
+        if (pullTagId == -1) {
+            invalidConfigReason = InvalidConfigReason(
+                    INVALID_CONFIG_REASON_GAUGE_METRIC_PUSHED_WITH_PULL_PROBABILITY, metric.id());
+            return nullopt;
+        }
+        if (metric.sampling_type() == GaugeMetric::RANDOM_ONE_SAMPLE) {
+            invalidConfigReason = InvalidConfigReason(
+                    INVALID_CONFIG_REASON_GAUGE_METRIC_RANDOM_ONE_SAMPLE_WITH_PULL_PROBABILITY,
+                    metric.id());
+            return nullopt;
+        }
+    }
+
     unordered_map<int, shared_ptr<Activation>> eventActivationMap;
     unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
     invalidConfigReason = handleMetricActivation(
@@ -1218,19 +1371,21 @@
     }
 
     const auto [dimensionSoftLimit, dimensionHardLimit] =
-            StatsdStats::getAtomDimensionKeySizeLimits(pullTagId);
+            StatsdStats::getAtomDimensionKeySizeLimits(
+                    pullTagId,
+                    StatsdStats::clampDimensionKeySizeLimit(metric.max_dimensions_per_bucket()));
 
     sp<MetricProducer> metricProducer = new GaugeMetricProducer(
             key, metric, conditionIndex, initialConditionCache, wizard, metricHash, trackerIndex,
             matcherWizard, pullTagId, triggerAtomId, atomTagId, timeBaseNs, currentTimeNs,
-            pullerManager, eventActivationMap, eventDeactivationMap, dimensionSoftLimit,
-            dimensionHardLimit);
+            pullerManager, configMetadataProvider, eventActivationMap, eventDeactivationMap,
+            dimensionSoftLimit, dimensionHardLimit);
 
     SamplingInfo samplingInfo;
     std::vector<Matcher> dimensionsInWhat;
     translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
     if (metric.has_dimensional_sampling_info()) {
-        invalidConfigReason = handleMetricWithSampling(
+        invalidConfigReason = handleMetricWithDimensionalSampling(
                 metric.id(), metric.dimensional_sampling_info(), dimensionsInWhat, samplingInfo);
         if (invalidConfigReason.has_value()) {
             return nullopt;
@@ -1295,7 +1450,7 @@
     for (int i = 0; i < atomMatcherCount; i++) {
         const AtomMatcher& logMatcher = config.atom_matcher(i);
         sp<AtomMatchingTracker> tracker =
-                createAtomMatchingTracker(logMatcher, i, uidMap, invalidConfigReason);
+                createAtomMatchingTracker(logMatcher, uidMap, invalidConfigReason);
         if (tracker == nullptr) {
             return invalidConfigReason;
         }
@@ -1309,11 +1464,12 @@
         matcherConfigs.push_back(logMatcher);
     }
 
-    vector<bool> stackTracker2(allAtomMatchingTrackers.size(), false);
+    vector<uint8_t> stackTracker2(allAtomMatchingTrackers.size(), false);
     for (size_t matcherIndex = 0; matcherIndex < allAtomMatchingTrackers.size(); matcherIndex++) {
         auto& matcher = allAtomMatchingTrackers[matcherIndex];
-        invalidConfigReason = matcher->init(matcherConfigs, allAtomMatchingTrackers,
-                                            atomMatchingTrackerMap, stackTracker2);
+        const auto [invalidConfigReason, _] =
+                matcher->init(matcherIndex, matcherConfigs, allAtomMatchingTrackers,
+                              atomMatchingTrackerMap, stackTracker2);
         if (invalidConfigReason.has_value()) {
             return invalidConfigReason;
         }
@@ -1370,7 +1526,7 @@
         conditionConfigs.push_back(condition);
     }
 
-    vector<bool> stackTracker(allConditionTrackers.size(), false);
+    vector<uint8_t> stackTracker(allConditionTrackers.size(), false);
     for (size_t i = 0; i < allConditionTrackers.size(); i++) {
         auto& conditionTracker = allConditionTrackers[i];
         invalidConfigReason =
@@ -1405,8 +1561,8 @@
         stateProtoHashes[stateId] = Hash64(serializedState);
 
         const StateMap& stateMap = state.map();
-        for (auto group : stateMap.group()) {
-            for (auto value : group.value()) {
+        for (const auto& group : stateMap.group()) {
+            for (const auto& value : group.value()) {
                 allStateGroupMaps[stateId][value] = group.group_id();
             }
         }
@@ -1431,7 +1587,8 @@
         std::set<int64_t>& noReportMetricIds,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation) {
+        vector<int>& metricsWithActivation,
+        const wp<ConfigMetadataProvider> configMetadataProvider) {
     sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
     sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers);
     const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
@@ -1440,6 +1597,12 @@
     allMetricProducers.reserve(allMetricsCount);
     optional<InvalidConfigReason> invalidConfigReason;
 
+    if (config.has_restricted_metrics_delegate_package_name() &&
+        allMetricsCount != config.event_metric_size()) {
+        ALOGE("Restricted metrics only support event metric");
+        return InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED);
+    }
+
     // Construct map from metric id to metric activation index. The map will be used to determine
     // the metric activation corresponding to a metric.
     unordered_map<int64_t, int> metricToActivationMap;
@@ -1466,7 +1629,7 @@
                 conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap,
                 allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, invalidConfigReason);
+                metricsWithActivation, invalidConfigReason, configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1485,7 +1648,7 @@
                 conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap,
                 allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, invalidConfigReason);
+                metricsWithActivation, invalidConfigReason, configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1502,7 +1665,8 @@
                 atomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap,
                 initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap,
                 conditionToMetricMap, activationAtomTrackerToMetricMap,
-                deactivationAtomTrackerToMetricMap, metricsWithActivation, invalidConfigReason);
+                deactivationAtomTrackerToMetricMap, metricsWithActivation, invalidConfigReason,
+                configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1520,7 +1684,7 @@
                 conditionTrackerMap, initialConditionCache, wizard, matcherWizard, stateAtomIdMap,
                 allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, invalidConfigReason);
+                metricsWithActivation, invalidConfigReason, configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1538,7 +1702,7 @@
                 conditionTrackerMap, initialConditionCache, wizard, matcherWizard, stateAtomIdMap,
                 allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, invalidConfigReason);
+                metricsWithActivation, invalidConfigReason, configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1556,7 +1720,7 @@
                 conditionTrackerMap, initialConditionCache, wizard, matcherWizard,
                 metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-                metricsWithActivation, invalidConfigReason);
+                metricsWithActivation, invalidConfigReason, configMetadataProvider);
         if (!producer) {
             return invalidConfigReason;
         }
@@ -1644,7 +1808,7 @@
         const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
         const sp<StatsPullerManager>& pullerManager, const sp<AlarmMonitor>& anomalyAlarmMonitor,
         const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
-        const int64_t currentTimeNs,
+        const int64_t currentTimeNs, const wp<ConfigMetadataProvider> configMetadataProvider,
         std::unordered_map<int, std::vector<int>>& allTagIdsToMatchersMap,
         vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         unordered_map<int64_t, int>& atomMatchingTrackerMap,
@@ -1700,7 +1864,7 @@
             allConditionTrackers, initialConditionCache, allMetricProducers, conditionToMetricMap,
             trackerToMetricMap, metricProducerMap, noReportMetricIds,
             activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
-            metricsWithActivation);
+            metricsWithActivation, configMetadataProvider);
     if (invalidConfigReason.has_value()) {
         ALOGE("initMetricProducers failed");
         return invalidConfigReason;
diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index c4e231a..8a73ff0 100644
--- a/statsd/src/metrics/parsing_utils/metrics_manager_util.h
+++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -22,6 +22,7 @@
 
 #include "anomaly/AlarmTracker.h"
 #include "condition/ConditionTracker.h"
+#include "config/ConfigMetadataProvider.h"
 #include "external/StatsPullerManager.h"
 #include "matchers/AtomMatchingTracker.h"
 #include "metrics/MetricProducer.h"
@@ -36,12 +37,11 @@
 // Create a AtomMatchingTracker.
 // input:
 // [logMatcher]: the input AtomMatcher from the StatsdConfig
-// [index]: the index of the matcher
 // [invalidConfigReason]: logging ids if config is invalid
 // output:
 // new AtomMatchingTracker, or null if the tracker is unable to be created
 sp<AtomMatchingTracker> createAtomMatchingTracker(
-        const AtomMatcher& logMatcher, const int index, const sp<UidMap>& uidMap,
+        const AtomMatcher& logMatcher, const sp<UidMap>& uidMap,
         optional<InvalidConfigReason>& invalidConfigReason);
 
 // Create a ConditionTracker.
@@ -53,21 +53,20 @@
 // output:
 // new ConditionTracker, or null if the tracker is unable to be created
 sp<ConditionTracker> createConditionTracker(
-        const ConfigKey& key, const Predicate& predicate, const int index,
+        const ConfigKey& key, const Predicate& predicate, int index,
         const unordered_map<int64_t, int>& atomMatchingTrackerMap,
         optional<InvalidConfigReason>& invalidConfigReason);
 
 // Get the hash of a metric, combining the activation if the metric has one.
 optional<InvalidConfigReason> getMetricProtoHash(
-        const StatsdConfig& config, const google::protobuf::MessageLite& metric, const int64_t id,
+        const StatsdConfig& config, const google::protobuf::MessageLite& metric, int64_t id,
         const std::unordered_map<int64_t, int>& metricToActivationMap, uint64_t& metricHash);
 
 // 1. Validates matcher existence
 // 2. Enforces matchers with dimensions and those used for trigger_event are about one atom
 // 3. Gets matcher index and updates tracker to metric map
 optional<InvalidConfigReason> handleMetricWithAtomMatchingTrackers(
-        const int64_t matcherId, const int64_t metricId, const int metricIndex,
-        const bool enforceOneAtom,
+        const int64_t matcherId, int64_t metricId, int metricIndex, const bool enforceOneAtom,
         const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
         std::unordered_map<int, std::vector<int>>& trackerToMetricMap, int& logTrackerIndex);
@@ -75,7 +74,7 @@
 // 1. Validates condition existence, including those in links
 // 2. Gets condition index and updates condition to metric map
 optional<InvalidConfigReason> handleMetricWithConditions(
-        const int64_t condition, const int64_t metricId, const int metricIndex,
+        const int64_t condition, int64_t metricId, int metricIndex,
         const std::unordered_map<int64_t, int>& conditionTrackerMap,
         const ::google::protobuf::RepeatedPtrField<MetricConditionLink>& links,
         const std::vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex,
@@ -85,7 +84,7 @@
 // Fills the new event activation/deactivation maps, preserving the existing activations.
 // Returns nullopt if successful and InvalidConfigReason if not.
 optional<InvalidConfigReason> handleMetricActivationOnConfigUpdate(
-        const StatsdConfig& config, const int64_t metricId, const int metricIndex,
+        const StatsdConfig& config, int64_t metricId, int metricIndex,
         const std::unordered_map<int64_t, int>& metricToActivationMap,
         const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
         const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
@@ -99,8 +98,8 @@
 // Creates a CountMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
 optional<sp<MetricProducer>> createCountMetricProducerAndUpdateMetadata(
-        const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
-        const int64_t currentTimeNs, const CountMetric& metric, const int metricIndex,
+        const ConfigKey& key, const StatsdConfig& config, int64_t timeBaseNs,
+        const int64_t currentTimeNs, const CountMetric& metric, int metricIndex,
         const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
         std::vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -113,14 +112,14 @@
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation,
-        optional<InvalidConfigReason>& invalidConfigReason);
+        std::vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates a DurationMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
 optional<sp<MetricProducer>> createDurationMetricProducerAndUpdateMetadata(
-        const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
-        const int64_t currentTimeNs, const DurationMetric& metric, const int metricIndex,
+        const ConfigKey& key, const StatsdConfig& config, int64_t timeBaseNs,
+        const int64_t currentTimeNs, const DurationMetric& metric, int metricIndex,
         const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
         std::vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -133,14 +132,14 @@
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation,
-        optional<InvalidConfigReason>& invalidConfigReason);
+        std::vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates an EventMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
 optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata(
-        const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
-        const EventMetric& metric, const int metricIndex,
+        const ConfigKey& key, const StatsdConfig& config, int64_t timeBaseNs,
+        const EventMetric& metric, int metricIndex,
         const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
         std::vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -151,15 +150,15 @@
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation,
-        optional<InvalidConfigReason>& invalidConfigReason);
+        std::vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates a NumericValueMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
 optional<sp<MetricProducer>> createNumericValueMetricProducerAndUpdateMetadata(
-        const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+        const ConfigKey& key, const StatsdConfig& config, int64_t timeBaseNs,
         const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
-        const ValueMetric& metric, const int metricIndex,
+        const ValueMetric& metric, int metricIndex,
         const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
         std::vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -173,15 +172,15 @@
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation,
-        optional<InvalidConfigReason>& invalidConfigReason);
+        std::vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates a GaugeMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
 optional<sp<MetricProducer>> createGaugeMetricProducerAndUpdateMetadata(
-        const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+        const ConfigKey& key, const StatsdConfig& config, int64_t timeBaseNs,
         const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
-        const GaugeMetric& metric, const int metricIndex,
+        const GaugeMetric& metric, int metricIndex,
         const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
         std::vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -193,15 +192,15 @@
         std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation,
-        optional<InvalidConfigReason>& invalidConfigReason);
+        std::vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates a KllMetricProducer and updates the vectors/maps used by MetricsManager with
 // the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
 optional<sp<MetricProducer>> createKllMetricProducerAndUpdateMetadata(
-        const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+        const ConfigKey& key, const StatsdConfig& config, int64_t timeBaseNs,
         const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
-        const KllMetric& metric, const int metricIndex,
+        const KllMetric& metric, int metricIndex,
         const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         const unordered_map<int64_t, int>& atomMatchingTrackerMap,
         vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -215,13 +214,14 @@
         unordered_map<int, vector<int>>& conditionToMetricMap,
         unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
         unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
-        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason);
+        vector<int>& metricsWithActivation, optional<InvalidConfigReason>& invalidConfigReason,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Creates an AnomalyTracker and adds it to the appropriate metric.
 // Returns an sp to the AnomalyTracker, or nullopt if there was an error.
 optional<sp<AnomalyTracker>> createAnomalyTracker(
         const Alert& alert, const sp<AlarmMonitor>& anomalyAlarmMonitor,
-        const UpdateStatus& updateStatus, const int64_t currentTimeNs,
+        const UpdateStatus& updateStatus, int64_t currentTimeNs,
         const std::unordered_map<int64_t, int>& metricProducerMap,
         std::vector<sp<MetricProducer>>& allMetricProducers,
         optional<InvalidConfigReason>& invalidConfigReason);
@@ -340,7 +340,7 @@
 // [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
 // Returns nullopt if successful and InvalidConfigReason if not.
 optional<InvalidConfigReason> initMetrics(
-        const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
+        const ConfigKey& key, const StatsdConfig& config, int64_t timeBaseTimeNs,
         const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
         const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
         const std::unordered_map<int64_t, int>& conditionTrackerMap,
@@ -355,13 +355,14 @@
         std::set<int64_t>& noReportMetricIds,
         std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
         std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
-        std::vector<int>& metricsWithActivation);
+        std::vector<int>& metricsWithActivation,
+        const wp<ConfigMetadataProvider> configMetadataProvider);
 
 // Initialize alarms
 // Is called both on initialize new configs and config updates since alarms do not have any state.
 optional<InvalidConfigReason> initAlarms(const StatsdConfig& config, const ConfigKey& key,
                                          const sp<AlarmMonitor>& periodicAlarmMonitor,
-                                         const int64_t timeBaseNs, const int64_t currentTimeNs,
+                                         const int64_t timeBaseNs, int64_t currentTimeNs,
                                          std::vector<sp<AlarmTracker>>& allAlarmTrackers);
 
 // Initialize MetricsManager from StatsdConfig.
@@ -369,8 +370,8 @@
 optional<InvalidConfigReason> initStatsdConfig(
         const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
         const sp<StatsPullerManager>& pullerManager, const sp<AlarmMonitor>& anomalyAlarmMonitor,
-        const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs,
-        const int64_t currentTimeNs,
+        const sp<AlarmMonitor>& periodicAlarmMonitor, int64_t timeBaseNs,
+        const int64_t currentTimeNs, const wp<ConfigMetadataProvider> configMetadataProvider,
         std::unordered_map<int, std::vector<int>>& allTagIdsToMatchersMap,
         std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
         std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
diff --git a/statsd/src/packages/PackageInfoListener.h b/statsd/src/packages/PackageInfoListener.h
index 485f9fd..d796774 100644
--- a/statsd/src/packages/PackageInfoListener.h
+++ b/statsd/src/packages/PackageInfoListener.h
@@ -29,15 +29,14 @@
 public:
     // Uid map will notify this listener that the app with apk name and uid has been upgraded to
     // the specified version.
-    virtual void notifyAppUpgrade(const int64_t& eventTimeNs, const std::string& apk,
-                                  const int uid, const int64_t version) = 0;
+    virtual void notifyAppUpgrade(int64_t eventTimeNs, const std::string& apk, const int uid,
+                                  int64_t version) = 0;
 
     // Notify interested listeners that the given apk and uid combination no longer exits.
-    virtual void notifyAppRemoved(const int64_t& eventTimeNs, const std::string& apk,
-                                  const int uid) = 0;
+    virtual void notifyAppRemoved(int64_t eventTimeNs, const std::string& apk, const int uid) = 0;
 
     // Notify the listener that the UidMap snapshot is available.
-    virtual void onUidMapReceived(const int64_t& eventTimeNs) = 0;
+    virtual void onUidMapReceived(int64_t eventTimeNs) = 0;
 };
 
 }  // namespace statsd
diff --git a/statsd/src/packages/UidMap.cpp b/statsd/src/packages/UidMap.cpp
index aa43e8d..79e8534 100644
--- a/statsd/src/packages/UidMap.cpp
+++ b/statsd/src/packages/UidMap.cpp
@@ -25,7 +25,6 @@
 
 using namespace android;
 
-using android::base::StringPrintf;
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_BYTES;
@@ -94,12 +93,12 @@
     return normalizedName;
 }
 
-std::set<string> UidMap::getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const {
+std::set<string> UidMap::getAppNamesFromUid(const int32_t uid, bool returnNormalized) const {
     lock_guard<mutex> lock(mMutex);
     return getAppNamesFromUidLocked(uid,returnNormalized);
 }
 
-std::set<string> UidMap::getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const {
+std::set<string> UidMap::getAppNamesFromUidLocked(const int32_t uid, bool returnNormalized) const {
     std::set<string> names;
     for (const auto& kv : mMap) {
         if (kv.first.first == uid && !kv.second.deleted) {
@@ -119,7 +118,7 @@
     return it->second.versionCode;
 }
 
-void UidMap::updateMap(const int64_t& timestamp, const UidData& uidData) {
+void UidMap::updateMap(const int64_t timestamp, const UidData& uidData) {
     wp<PackageInfoListener> broadcast = NULL;
     {
         lock_guard<mutex> lock(mMutex);  // Exclusively lock for updates.
@@ -157,13 +156,13 @@
     // itself before we call it. It's then the listener's job to handle it (expect the callback to
     // be called after listener is removed, and the listener should properly ignore it).
     auto strongPtr = broadcast.promote();
-    if (strongPtr != NULL) {
+    if (strongPtr != nullptr) {
         strongPtr->onUidMapReceived(timestamp);
     }
 }
 
-void UidMap::updateApp(const int64_t& timestamp, const string& appName, const int32_t& uid,
-                       const int64_t& versionCode, const string& versionString,
+void UidMap::updateApp(const int64_t timestamp, const string& appName, const int32_t uid,
+                       const int64_t versionCode, const string& versionString,
                        const string& installer, const vector<uint8_t>& certificateHash) {
     wp<PackageInfoListener> broadcast = NULL;
 
@@ -202,7 +201,7 @@
     }
 
     auto strongPtr = broadcast.promote();
-    if (strongPtr != NULL) {
+    if (strongPtr != nullptr) {
         strongPtr->notifyAppUpgrade(timestamp, appName, uid, versionCode);
     }
 }
@@ -224,7 +223,7 @@
     }
 }
 
-void UidMap::removeApp(const int64_t& timestamp, const string& app, const int32_t& uid) {
+void UidMap::removeApp(const int64_t timestamp, const string& app, const int32_t uid) {
     wp<PackageInfoListener> broadcast = NULL;
     {
         lock_guard<mutex> lock(mMutex);
@@ -255,12 +254,12 @@
     }
 
     auto strongPtr = broadcast.promote();
-    if (strongPtr != NULL) {
+    if (strongPtr != nullptr) {
         strongPtr->notifyAppRemoved(timestamp, app, uid);
     }
 }
 
-void UidMap::setListener(wp<PackageInfoListener> listener) {
+void UidMap::setListener(const wp<PackageInfoListener>& listener) {
     lock_guard<mutex> lock(mMutex);  // Lock for updates
     mSubscriber = listener;
 }
@@ -409,7 +408,7 @@
     }
 }
 
-void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key,
+void UidMap::appendUidMap(const int64_t timestamp, const ConfigKey& key,
                           const bool includeVersionStrings, const bool includeInstaller,
                           const uint8_t truncatedCertificateHashSize, std::set<string>* str_set,
                           ProtoOutputStream* proto) {
@@ -639,6 +638,7 @@
                                                              {"AID_SDK_SANDBOX", 1090},
                                                              {"AID_SECURITY_LOG_WRITER", 1091},
                                                              {"AID_PRNG_SEEDER", 1092},
+                                                             {"AID_UPROBESTATS", 1093},
                                                              {"AID_SHELL", 2000},
                                                              {"AID_CACHE", 2001},
                                                              {"AID_DIAG", 2002},
diff --git a/statsd/src/packages/UidMap.h b/statsd/src/packages/UidMap.h
index 72330f5..45429cb 100644
--- a/statsd/src/packages/UidMap.h
+++ b/statsd/src/packages/UidMap.h
@@ -73,9 +73,9 @@
     const string versionString;
     const string prevVersionString;
 
-    ChangeRecord(const bool isDeletion, const int64_t timestampNs, const string& package,
-                 const int32_t uid, const int64_t version, const string versionString,
-                 const int64_t prevVersion, const string prevVersionString)
+    ChangeRecord(const bool isDeletion, int64_t timestampNs, const string& package,
+                 const int32_t uid, int64_t version, const string& versionString,
+                 const int64_t prevVersion, const string& prevVersionString)
         : deletion(isDeletion),
           timestampNs(timestampNs),
           package(package),
@@ -99,18 +99,18 @@
 
     static sp<UidMap> getInstance();
 
-    void updateMap(const int64_t& timestamp, const UidData& uidData);
+    void updateMap(const int64_t timestamp, const UidData& uidData);
 
-    void updateApp(const int64_t& timestamp, const string& appName, const int32_t& uid,
-                   const int64_t& versionCode, const string& versionString, const string& installer,
+    void updateApp(const int64_t timestamp, const string& appName, const int32_t uid,
+                   const int64_t versionCode, const string& versionString, const string& installer,
                    const vector<uint8_t>& certificateHash);
-    void removeApp(const int64_t& timestamp, const string& app, const int32_t& uid);
+    void removeApp(const int64_t timestamp, const string& app, const int32_t uid);
 
     // Returns true if the given uid contains the specified app (eg. com.google.android.gms).
     bool hasApp(int uid, const string& packageName) const;
 
     // Returns the app names from uid.
-    std::set<string> getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const;
+    std::set<string> getAppNamesFromUid(int32_t uid, bool returnNormalized) const;
 
     int64_t getAppVersion(int uid, const string& packageName) const;
 
@@ -121,7 +121,7 @@
     // Command for indicating to the map that StatsLogProcessor should be notified if an app is
     // updated. This allows metric producers and managers to distinguish when the same uid or app
     // represents a different version of an app.
-    void setListener(wp<PackageInfoListener> listener);
+    void setListener(const wp<PackageInfoListener>& listener);
 
     // Informs uid map that a config is added/updated. Used for keeping mConfigKeys up to date.
     void OnConfigUpdated(const ConfigKey& key);
@@ -138,10 +138,9 @@
     // Gets all snapshots and changes that have occurred since the last output.
     // If every config key has received a change or snapshot record, then this
     // record is deleted.
-    void appendUidMap(const int64_t& timestamp, const ConfigKey& key,
-                      const bool includeVersionStrings, const bool includeInstaller,
-                      const uint8_t truncatedCertificateHashSize, std::set<string>* str_set,
-                      ProtoOutputStream* proto);
+    void appendUidMap(int64_t timestamp, const ConfigKey& key, const bool includeVersionStrings,
+                      const bool includeInstaller, const uint8_t truncatedCertificateHashSize,
+                      std::set<string>* str_set, ProtoOutputStream* proto);
 
     // Forces the output to be cleared. We still generate a snapshot based on the current state.
     // This results in extra data uploaded but helps us reconstruct the uid mapping on the server
@@ -165,7 +164,7 @@
                              ProtoOutputStream* proto) const;
 
 private:
-    std::set<string> getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const;
+    std::set<string> getAppNamesFromUidLocked(int32_t uid, bool returnNormalized) const;
     string normalizeAppName(const string& appName) const;
 
     void writeUidMapSnapshotLocked(const int64_t timestamp, const bool includeVersionStrings,
@@ -179,7 +178,7 @@
     mutable mutex mIsolatedMutex;
 
     struct PairHash {
-        size_t operator()(std::pair<int, string> p) const noexcept {
+        size_t operator()(const std::pair<int, string>& p) const noexcept {
             std::hash<std::string> hash_fn;
             return hash_fn(std::to_string(p.first) + p.second);
         }
@@ -221,6 +220,9 @@
     size_t mBytesUsed;
 
     // Allows unit-test to access private methods.
+    FRIEND_TEST(RestrictedEventMetricE2eTest, TestRestrictedConfigUpdateDoesNotUpdateUidMap);
+    FRIEND_TEST(RestrictedEventMetricE2eTest,
+                TestRestrictedConfigUpdateAddsDelegateRemovesUidMapEntry);
     FRIEND_TEST(UidMapTest, TestClearingOutput);
     FRIEND_TEST(UidMapTest, TestRemovedAppRetained);
     FRIEND_TEST(UidMapTest, TestRemovedAppOverGuardrail);
diff --git a/statsd/src/shell/ShellSubscriber.cpp b/statsd/src/shell/ShellSubscriber.cpp
index 0a21c9a..56c3ccd 100644
--- a/statsd/src/shell/ShellSubscriber.cpp
+++ b/statsd/src/shell/ShellSubscriber.cpp
@@ -24,6 +24,7 @@
 
 #include "guardrail/StatsdStats.h"
 #include "stats_log_util.h"
+#include "utils/api_tracing.h"
 
 using aidl::android::os::IStatsSubscriptionCallback;
 
@@ -126,10 +127,15 @@
 }
 
 void ShellSubscriber::onLogEvent(const LogEvent& event) {
+    ATRACE_CALL();
     // Skip if event is skipped
     if (event.isParsedHeaderOnly()) {
         return;
     }
+    // Skip RestrictedLogEvents
+    if (event.isRestricted()) {
+        return;
+    }
     std::unique_lock<std::mutex> lock(mMutex);
     for (auto clientIt = mClientSet.begin(); clientIt != mClientSet.end();) {
         (*clientIt)->onLogEvent(event);
@@ -193,9 +199,6 @@
 
 void ShellSubscriber::updateLogEventFilterLocked() const {
     VLOG("ShellSubscriber: Updating allAtomIds");
-    if (!mLogEventFilter) {
-        return;
-    }
     LogEventFilter::AtomIdSet allAtomIds;
     for (const auto& client : mClientSet) {
         client->addAllAtomIds(allAtomIds);
diff --git a/statsd/src/shell/ShellSubscriber.h b/statsd/src/shell/ShellSubscriber.h
index 4ff8861..5e54fbf 100644
--- a/statsd/src/shell/ShellSubscriber.h
+++ b/statsd/src/shell/ShellSubscriber.h
@@ -56,7 +56,7 @@
  */
 class ShellSubscriber : public virtual RefBase {
 public:
-    ShellSubscriber(sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr,
+    ShellSubscriber(const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerMgr,
                     const std::shared_ptr<LogEventFilter>& logEventFilter)
         : mUidMap(uidMap), mPullerMgr(pullerMgr), mLogEventFilter(logEventFilter){};
 
diff --git a/statsd/src/shell/ShellSubscriberClient.cpp b/statsd/src/shell/ShellSubscriberClient.cpp
index aaa4126..67f7eb1 100644
--- a/statsd/src/shell/ShellSubscriberClient.cpp
+++ b/statsd/src/shell/ShellSubscriberClient.cpp
@@ -24,7 +24,6 @@
 #include "stats_log_util.h"
 
 using android::base::unique_fd;
-using android::util::ProtoOutputStream;
 using Status = ::ndk::ScopedAStatus;
 
 namespace android {
@@ -177,23 +176,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/shell/ShellSubscriberClient.h b/statsd/src/shell/ShellSubscriberClient.h
index 2aab6eb..9d1724a 100644
--- a/statsd/src/shell/ShellSubscriberClient.h
+++ b/statsd/src/shell/ShellSubscriberClient.h
@@ -94,14 +94,13 @@
         return kMaxSizeKb;
     }
 
+    void addAllAtomIds(LogEventFilter::AtomIdSet& allAtomIds) const;
+
     // Minimum pull interval for callback subscriptions.
     static constexpr int64_t kMinCallbackPullIntervalMs = 60'000;  // 60 seconds.
 
     // Minimum sleep for the pull thread for callback subscriptions.
     static constexpr int64_t kMinCallbackSleepIntervalMs = 2000;  // 2 seconds.
-
-    void addAllAtomIds(LogEventFilter::AtomIdSet& allAtomIds) const;
-
 private:
     int64_t pullIfNeeded(int64_t nowSecs, int64_t nowMillis, int64_t nowNanos);
 
diff --git a/statsd/src/socket/LogEventFilter.h b/statsd/src/socket/LogEventFilter.h
index aec91ef..941e359 100644
--- a/statsd/src/socket/LogEventFilter.h
+++ b/statsd/src/socket/LogEventFilter.h
@@ -114,15 +114,12 @@
     FRIEND_TEST(LogEventFilterTest, TestEmptyFilter);
     FRIEND_TEST(LogEventFilterTest, TestRemoveNonExistingEmptyFilter);
     FRIEND_TEST(LogEventFilterTest, TestEmptyFilterDisabled);
-    FRIEND_TEST(LogEventFilterTest, TestEmptyFilterDisabledSetter);
     FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterFullOverlap);
     FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterPartialOverlap);
     FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterDisabled);
     FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterDisabledPartialOverlap);
     FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerOverlapIds);
-    FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerNonOverlapIds);
     FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerOverlapIdsRemoved);
-    FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerNonOverlapIdsRemoved);
     FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerEmptyFilter);
 };
 
diff --git a/statsd/src/socket/StatsSocketListener.cpp b/statsd/src/socket/StatsSocketListener.cpp
index 5873df3..7f0eb43 100644
--- a/statsd/src/socket/StatsSocketListener.cpp
+++ b/statsd/src/socket/StatsSocketListener.cpp
@@ -16,7 +16,10 @@
 #define STATSD_DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
+#include "StatsSocketListener.h"
+
 #include <ctype.h>
+#include <cutils/sockets.h>
 #include <limits.h>
 #include <stdio.h>
 #include <sys/cdefs.h>
@@ -26,24 +29,25 @@
 #include <sys/un.h>
 #include <unistd.h>
 
-#include <cutils/sockets.h>
-
-#include "StatsSocketListener.h"
 #include "guardrail/StatsdStats.h"
+#include "logd/logevent_util.h"
 #include "stats_log_util.h"
+#include "statslog_statsd.h"
+#include "utils/api_tracing.h"
 
 namespace android {
 namespace os {
 namespace statsd {
 
-StatsSocketListener::StatsSocketListener(std::shared_ptr<LogEventQueue> queue,
+StatsSocketListener::StatsSocketListener(const std::shared_ptr<LogEventQueue>& queue,
                                          const std::shared_ptr<LogEventFilter>& logEventFilter)
     : SocketListener(getLogSocket(), false /*start listen*/),
-      mQueue(std::move(queue)),
+      mQueue(queue),
       mLogEventFilter(logEventFilter) {
 }
 
 bool StatsSocketListener::onDataAvailable(SocketClient* cli) {
+    ATRACE_CALL();
     static bool name_set;
     if (!name_set) {
         prctl(PR_SET_NAME, "statsd.writer");
@@ -59,7 +63,7 @@
             NULL, 0, &iov, 1, control, sizeof(control), 0,
     };
 
-    int socket = cli->getSocket();
+    const int socket = cli->getSocket();
 
     // To clear the entire buffer is secure/safe, but this contributes to 1.68%
     // overhead under logging load. We are safe because we check counts, but
@@ -90,8 +94,26 @@
         cred->uid = DEFAULT_OVERFLOWUID;
     }
 
-    uint8_t* ptr = ((uint8_t*)buffer) + sizeof(android_log_header_t);
-    n -= sizeof(android_log_header_t);
+    const uint32_t uid = cred->uid;
+    const uint32_t pid = cred->pid;
+
+    processSocketMessage(buffer, n, uid, pid, *mQueue, *mLogEventFilter);
+
+    return true;
+}
+
+void StatsSocketListener::processSocketMessage(const char* buffer, const uint32_t len, uint32_t uid,
+                                               uint32_t pid, LogEventQueue& queue,
+                                               const LogEventFilter& filter) {
+    ATRACE_CALL();
+    static const uint32_t kStatsEventTag = 1937006964;
+
+    if (len <= (ssize_t)(sizeof(android_log_header_t)) + sizeof(uint32_t)) {
+        return;
+    }
+
+    const uint8_t* ptr = ((uint8_t*)buffer) + sizeof(android_log_header_t);
+    uint32_t bufferLen = len - sizeof(android_log_header_t);
 
     // When a log failed to write to statsd socket (e.g., due ot EBUSY), a special message would
     // be sent to statsd when the socket communication becomes available again.
@@ -100,8 +122,9 @@
     // Note that all normal stats logs are in the format of event_list, so there won't be confusion.
     //
     // TODO(b/80538532): In addition to log it in StatsdStats, we should properly reset the config.
-    if (n == sizeof(android_log_event_long_t)) {
-        android_log_event_long_t* long_event = reinterpret_cast<android_log_event_long_t*>(ptr);
+    if (bufferLen == sizeof(android_log_event_long_t)) {
+        const android_log_event_long_t* long_event =
+                reinterpret_cast<const android_log_event_long_t*>(ptr);
         if (long_event->payload.type == EVENT_TYPE_LONG) {
             int64_t composed_long = long_event->payload.data;
 
@@ -111,33 +134,35 @@
             int32_t last_atom_tag = (int32_t)((0xffffffff00000000 & (uint64_t)composed_long) >> 32);
 
             ALOGE("Found dropped events: %d error %d last atom tag %d from uid %d", dropped_count,
-                  long_event->header.tag, last_atom_tag, cred->uid);
+                  long_event->header.tag, last_atom_tag, uid);
             StatsdStats::getInstance().noteLogLost((int32_t)getWallClockSec(), dropped_count,
-                                                   long_event->header.tag, last_atom_tag, cred->uid,
-                                                   cred->pid);
-            return true;
+                                                   long_event->header.tag, last_atom_tag, uid, pid);
+            return;
         }
     }
 
+    // test that received valid StatsEvent buffer
+    const uint32_t statsEventTag = *reinterpret_cast<const uint32_t*>(ptr);
+    if (statsEventTag != kStatsEventTag) {
+        return;
+    }
+
     // move past the 4-byte StatsEventTag
     const uint8_t* msg = ptr + sizeof(uint32_t);
-    const uint32_t len = n - sizeof(uint32_t);
-    const uint32_t uid = cred->uid;
-    const uint32_t pid = cred->pid;
+    bufferLen -= sizeof(uint32_t);
 
-    processMessage(msg, len, uid, pid, mQueue, mLogEventFilter);
-
-    return true;
+    processStatsEventBuffer(msg, bufferLen, uid, pid, queue, filter);
 }
 
-void StatsSocketListener::processMessage(const uint8_t* msg, uint32_t len, uint32_t uid,
-                                         uint32_t pid, const std::shared_ptr<LogEventQueue>& queue,
-                                         const std::shared_ptr<LogEventFilter>& filter) {
+void StatsSocketListener::processStatsEventBuffer(const uint8_t* msg, const uint32_t len,
+                                                  uint32_t uid, uint32_t pid, LogEventQueue& queue,
+                                                  const LogEventFilter& filter) {
+    ATRACE_CALL();
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(uid, pid);
 
-    if (filter && filter->getFilteringEnabled()) {
+    if (filter.getFilteringEnabled()) {
         const LogEvent::BodyBufferInfo bodyInfo = logEvent->parseHeader(msg, len);
-        if (filter->isAtomInUse(logEvent->GetTagId())) {
+        if (filter.isAtomInUse(logEvent->GetTagId())) {
             logEvent->parseBody(bodyInfo);
         }
     } else {
@@ -146,8 +171,27 @@
 
     const int32_t atomId = logEvent->GetTagId();
     const bool isAtomSkipped = logEvent->isParsedHeaderOnly();
-    int64_t oldestTimestamp;
-    if (!queue->push(std::move(logEvent), &oldestTimestamp)) {
+    const int64_t atomTimestamp = logEvent->GetElapsedTimestampNs();
+
+    if (atomId == util::STATS_SOCKET_LOSS_REPORTED) {
+        if (isAtomSkipped) {
+            ALOGW("Atom STATS_SOCKET_LOSS_REPORTED should not be skipped");
+        }
+
+        // handling socket loss info reported atom
+        // processing it here to not lose info due to queue overflow
+        const std::optional<SocketLossInfo>& lossInfo = toSocketLossInfo(*logEvent);
+        if (lossInfo) {
+            StatsdStats::getInstance().noteAtomSocketLoss(*lossInfo);
+        } else {
+            ALOGW("Atom STATS_SOCKET_LOSS_REPORTED content is invalid");
+        }
+    }
+
+    const auto [success, oldestTimestamp, queueSize] = queue.push(std::move(logEvent));
+    if (success) {
+        StatsdStats::getInstance().noteEventQueueSize(queueSize, atomTimestamp);
+    } else {
         StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp, atomId, isAtomSkipped);
     }
 }
diff --git a/statsd/src/socket/StatsSocketListener.h b/statsd/src/socket/StatsSocketListener.h
index 74a338e..2142e9e 100644
--- a/statsd/src/socket/StatsSocketListener.h
+++ b/statsd/src/socket/StatsSocketListener.h
@@ -38,7 +38,7 @@
 
 class StatsSocketListener : public SocketListener, public virtual RefBase {
 public:
-    explicit StatsSocketListener(std::shared_ptr<LogEventQueue> queue,
+    explicit StatsSocketListener(const std::shared_ptr<LogEventQueue>& queue,
                                  const std::shared_ptr<LogEventFilter>& logEventFilter);
 
     virtual ~StatsSocketListener() = default;
@@ -50,6 +50,21 @@
     static int getLogSocket();
 
     /**
+     * @brief Helper API to parse raw socket data buffer, make the LogEvent & submit it into the
+     * queue. Performs preliminary data validation.
+     * Created as a separate API to be easily tested without StatsSocketListener instance
+     *
+     * @param buffer buffer to parse
+     * @param len size of buffer in bytes
+     * @param uid arguments for LogEvent constructor
+     * @param pid arguments for LogEvent constructor
+     * @param queue queue to submit the event
+     * @param filter to be used for event evaluation
+     */
+    static void processSocketMessage(const char* buffer, uint32_t len, uint32_t uid, uint32_t pid,
+                                     LogEventQueue& queue, const LogEventFilter& filter);
+
+    /**
      * @brief Helper API to parse buffer, make the LogEvent & submit it into the queue
      * Created as a separate API to be easily tested without StatsSocketListener instance
      *
@@ -60,9 +75,9 @@
      * @param queue queue to submit the event
      * @param filter to be used for event evaluation
      */
-    static void processMessage(const uint8_t* msg, uint32_t len, uint32_t uid, uint32_t pid,
-                               const std::shared_ptr<LogEventQueue>& queue,
-                               const std::shared_ptr<LogEventFilter>& filter);
+    static void processStatsEventBuffer(const uint8_t* msg, uint32_t len, uint32_t uid,
+                                        uint32_t pid, LogEventQueue& queue,
+                                        const LogEventFilter& filter);
 
     /**
      * Who is going to get the events when they're read.
@@ -71,18 +86,18 @@
 
     std::shared_ptr<LogEventFilter> mLogEventFilter;
 
-    friend class SocketParseMessageTest;
-    friend void generateAtomLogging(const std::shared_ptr<LogEventQueue>& queue,
-                                    const std::shared_ptr<LogEventFilter>& filter, int eventCount,
-                                    int startAtomId);
+    friend void fuzzSocket(const uint8_t* data, size_t size);
 
-    FRIEND_TEST(SocketParseMessageTestNoFiltering, TestProcessMessageNoFiltering);
-    FRIEND_TEST(SocketParseMessageTestNoFiltering,
-                TestProcessMessageNoFilteringWithEmptySetExplicitSet);
-    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterEmptySet);
+    friend class SocketParseMessageTest;
+    friend void generateAtomLogging(LogEventQueue& queue, const LogEventFilter& filter,
+                                    int eventCount, int startAtomId);
+
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessage);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageEmptySetExplicitSet);
     FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterCompleteSet);
     FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterPartialSet);
     FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterToggle);
+    FRIEND_TEST(LogEventQueue_test, TestQueueMaxSize);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/state/StateManager.cpp b/statsd/src/state/StateManager.cpp
index e75bd97..1a32495 100644
--- a/statsd/src/state/StateManager.cpp
+++ b/statsd/src/state/StateManager.cpp
@@ -43,8 +43,9 @@
 void StateManager::onLogEvent(const LogEvent& event) {
     // Only process state events from uids in AID_* and packages that are whitelisted in
     // mAllowedPkg.
-    // Whitelisted AIDs are AID_ROOT and all AIDs in [1000, 2000)
-    if (event.GetUid() == AID_ROOT || (event.GetUid() >= 1000 && event.GetUid() < 2000) ||
+    // Allowlisted AIDs are AID_ROOT and all AIDs in [1000, 2000) which is [AID_SYSTEM, AID_SHELL)
+    if (event.GetUid() == AID_ROOT ||
+        (event.GetUid() >= AID_SYSTEM && event.GetUid() < AID_SHELL) ||
         mAllowedLogSources.find(event.GetUid()) != mAllowedLogSources.end()) {
         if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) {
             mStateTrackers[event.GetTagId()]->onLogEvent(event);
@@ -52,7 +53,7 @@
     }
 }
 
-void StateManager::registerListener(const int32_t atomId, wp<StateListener> listener) {
+void StateManager::registerListener(const int32_t atomId, const wp<StateListener>& listener) {
     // Check if state tracker already exists.
     if (mStateTrackers.find(atomId) == mStateTrackers.end()) {
         mStateTrackers[atomId] = new StateTracker(atomId);
@@ -60,7 +61,7 @@
     mStateTrackers[atomId]->registerListener(listener);
 }
 
-void StateManager::unregisterListener(const int32_t atomId, wp<StateListener> listener) {
+void StateManager::unregisterListener(const int32_t atomId, const wp<StateListener>& listener) {
     std::unique_lock<std::mutex> lock(mMutex);
 
     // Hold the sp<> until the lock is released so that ~StateTracker() is
diff --git a/statsd/src/state/StateManager.h b/statsd/src/state/StateManager.h
index 68b5c90..2db206e 100644
--- a/statsd/src/state/StateManager.h
+++ b/statsd/src/state/StateManager.h
@@ -55,17 +55,17 @@
     // If the correct StateTracker does not exist, a new StateTracker is created.
     // Note: StateTrackers can be created for non-state atoms. They are essentially empty and
     // do not perform any actions.
-    void registerListener(const int32_t atomId, wp<StateListener> listener);
+    void registerListener(const int32_t atomId, const wp<StateListener>& listener);
 
     // Notifies the correct StateTracker to unregister a listener
     // and removes the tracker if it no longer has any listeners.
-    void unregisterListener(const int32_t atomId, wp<StateListener> listener);
+    void unregisterListener(const int32_t atomId, const wp<StateListener>& listener);
 
     // Returns true if the StateTracker exists and queries for the
     // original state value mapped to the given query key. The state value is
     // stored and output in a FieldValue class.
     // Returns false if the StateTracker doesn't exist.
-    bool getStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+    bool getStateValue(int32_t atomId, const HashableDimensionKey& queryKey,
                        FieldValue* output) const;
 
     // Updates mAllowedLogSources with the latest uids for the packages that are allowed to log.
@@ -77,7 +77,7 @@
         return mStateTrackers.size();
     }
 
-    inline int getListenersCount(const int32_t atomId) const {
+    inline int getListenersCount(int32_t atomId) const {
         auto it = mStateTrackers.find(atomId);
         if (it != mStateTrackers.end()) {
             return it->second->getListenersCount();
diff --git a/statsd/src/state/StateTracker.cpp b/statsd/src/state/StateTracker.cpp
index 0feba42..d037e73 100644
--- a/statsd/src/state/StateTracker.cpp
+++ b/statsd/src/state/StateTracker.cpp
@@ -25,7 +25,7 @@
 namespace os {
 namespace statsd {
 
-StateTracker::StateTracker(const int32_t atomId) : mField(atomId, 0) {
+StateTracker::StateTracker(int32_t atomId) : mField(atomId, 0) {
 }
 
 void StateTracker::onLogEvent(const LogEvent& event) {
@@ -59,15 +59,14 @@
     }
 
     const bool nested = newState.mAnnotations.isNested();
-    StateValueInfo* stateValueInfo = &mStateMap[primaryKey];
-    updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, nested, stateValueInfo);
+    updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, nested, mStateMap[primaryKey]);
 }
 
-void StateTracker::registerListener(wp<StateListener> listener) {
+void StateTracker::registerListener(const wp<StateListener>& listener) {
     mListeners.insert(listener);
 }
 
-void StateTracker::unregisterListener(wp<StateListener> listener) {
+void StateTracker::unregisterListener(const wp<StateListener>& listener) {
     mListeners.erase(listener);
 }
 
@@ -90,7 +89,7 @@
     for (auto& [primaryKey, stateValueInfo] : mStateMap) {
         updateStateForPrimaryKey(eventTimeNs, primaryKey, newState,
                                  false /* nested; treat this state change as not nested */,
-                                 &stateValueInfo);
+                                 stateValueInfo);
     }
 }
 
@@ -106,36 +105,28 @@
     if (it != mStateMap.end()) {
         updateStateForPrimaryKey(eventTimeNs, primaryKey, state,
                                  false /* nested; treat this state change as not nested */,
-                                 &it->second);
+                                 it->second);
     }
 }
 
 void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs,
                                             const HashableDimensionKey& primaryKey,
                                             const FieldValue& newState, const bool nested,
-                                            StateValueInfo* stateValueInfo) {
+                                            StateValueInfo& stateValueInfo) {
     FieldValue oldState;
     oldState.mField = mField;
-    oldState.mValue.setInt(stateValueInfo->state);
-    const int32_t oldStateValue = stateValueInfo->state;
+    oldState.mValue.setInt(stateValueInfo.state);
+    const int32_t oldStateValue = stateValueInfo.state;
     const int32_t newStateValue = newState.mValue.int_value;
 
-    if (kStateUnknown == newStateValue) {
-        mStateMap.erase(primaryKey);
-    }
-
-    // Update state map for non-nested counting case.
+    // Update state map and notify listeners if state has changed.
     // Every state event triggers a state overwrite.
     if (!nested) {
-        stateValueInfo->state = newStateValue;
-        stateValueInfo->count = 1;
-
-        // Notify listeners if state has changed.
-        if (oldStateValue != newStateValue) {
+        if (newStateValue != oldStateValue) {
+            stateValueInfo.state = newStateValue;
+            stateValueInfo.count = 1;
             notifyListeners(eventTimeNs, primaryKey, oldState, newState);
         }
-        return;
-    }
 
     // Update state map for nested counting case.
     //
@@ -147,27 +138,34 @@
     // In atoms.proto, a state atom with nested counting enabled
     // must only have 2 states. There is no enforcemnt here of this requirement.
     // The atom must be logged correctly.
-    if (kStateUnknown == newStateValue) {
-        if (kStateUnknown != oldStateValue) {
+    } else if (newStateValue == kStateUnknown) {
+        if (oldStateValue != kStateUnknown) {
             notifyListeners(eventTimeNs, primaryKey, oldState, newState);
         }
     } else if (oldStateValue == kStateUnknown) {
-        stateValueInfo->state = newStateValue;
-        stateValueInfo->count = 1;
+        stateValueInfo.state = newStateValue;
+        stateValueInfo.count = 1;
         notifyListeners(eventTimeNs, primaryKey, oldState, newState);
     } else if (oldStateValue == newStateValue) {
-        stateValueInfo->count++;
-    } else if (--stateValueInfo->count == 0) {
-        stateValueInfo->state = newStateValue;
-        stateValueInfo->count = 1;
+        stateValueInfo.count++;
+    } else if (--stateValueInfo.count == 0) {
+        stateValueInfo.state = newStateValue;
+        stateValueInfo.count = 1;
         notifyListeners(eventTimeNs, primaryKey, oldState, newState);
     }
+
+    // Clear primary key entry from state map if state is now unknown.
+    // stateValueInfo points to a value in mStateMap and should not be accessed after erasing the
+    // entry
+    if (newStateValue == kStateUnknown) {
+        mStateMap.erase(primaryKey);
+    }
 }
 
 void StateTracker::notifyListeners(const int64_t eventTimeNs,
                                    const HashableDimensionKey& primaryKey,
                                    const FieldValue& oldState, const FieldValue& newState) {
-    for (auto l : mListeners) {
+    for (const auto& l : mListeners) {
         auto sl = l.promote();
         if (sl != nullptr) {
             sl->onStateChanged(eventTimeNs, mField.getTag(), primaryKey, oldState, newState);
diff --git a/statsd/src/state/StateTracker.h b/statsd/src/state/StateTracker.h
index abd579e..8e8f27f 100644
--- a/statsd/src/state/StateTracker.h
+++ b/statsd/src/state/StateTracker.h
@@ -16,20 +16,21 @@
 #pragma once
 
 #include <utils/RefBase.h>
+
+#include <set>
+#include <unordered_map>
+
 #include "HashableDimensionKey.h"
 #include "logd/LogEvent.h"
-
 #include "state/StateListener.h"
 
-#include <unordered_map>
-
 namespace android {
 namespace os {
 namespace statsd {
 
 class StateTracker : public virtual RefBase {
 public:
-    StateTracker(const int32_t atomId);
+    StateTracker(int32_t atomId);
 
     virtual ~StateTracker(){};
 
@@ -40,9 +41,9 @@
 
     // Adds new listeners to set of StateListeners. If a listener is already
     // registered, it is ignored.
-    void registerListener(wp<StateListener> listener);
+    void registerListener(const wp<StateListener>& listener);
 
-    void unregisterListener(wp<StateListener> listener);
+    void unregisterListener(const wp<StateListener>& listener);
 
     // The output is a FieldValue object that has mStateField as the field and
     // the original state value (found using the given query key) as the value.
@@ -80,7 +81,7 @@
     // Update the StateMap based on the received state value.
     void updateStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey,
                                   const FieldValue& newState, const bool nested,
-                                  StateValueInfo* stateValueInfo);
+                                  StateValueInfo& stateValueInfo);
 
     // Notify registered state listeners of state change.
     void notifyListeners(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey,
diff --git a/statsd/src/stats_log.proto b/statsd/src/stats_log.proto
index af21fe2..09ba6a8 100644
--- a/statsd/src/stats_log.proto
+++ b/statsd/src/stats_log.proto
@@ -318,6 +318,8 @@
 
   optional bool dimension_guardrail_hit = 17;
 
+  optional int64 estimated_data_bytes = 18;
+
   // Do not use.
   reserved 13, 15;
 }
@@ -400,7 +402,11 @@
 
   repeated string strings = 9;
 
-  repeated DataCorruptedReason data_corrupted_reason = 10;
+  reserved 10;
+
+  repeated DataCorruptedReason data_corrupted_reason = 11;
+
+  optional int64 estimated_data_bytes = 12;
 }
 
 message ConfigMetricsReportList {
@@ -412,6 +418,10 @@
 
   repeated ConfigMetricsReport reports = 2;
 
+  optional int32 report_number = 3;
+
+  optional int32 statsd_stats_id = 4;
+
   reserved 10 to 13, 101;
 }
 
@@ -480,6 +490,19 @@
         repeated Annotation annotation = 18;
         repeated int32 activation_time_sec = 22;
         repeated int32 deactivation_time_sec = 23;
+        repeated RestrictedMetricStats restricted_metric_stats = 25;
+        optional bool device_info_table_creation_failed = 26;
+        optional int32 restricted_db_corrupted_count = 27;
+        repeated int64 restricted_flush_latency = 28;
+        repeated int64 restricted_db_size_time_sec = 29;
+        repeated int64 restricted_db_size_bytes = 30;
+        repeated int32 dump_report_number = 31;
+        optional int32 db_deletion_stat_failed = 32;
+        optional int32 db_deletion_size_exceeded_limit = 33;
+        optional int32 db_deletion_config_invalid = 34;
+        optional int32 db_deletion_too_old = 35;
+        optional int32 db_deletion_config_removed = 36;
+        optional int32 db_deletion_config_updated = 37;
     }
 
     repeated ConfigStats config_stats = 3;
@@ -601,7 +624,29 @@
 
     repeated ActivationBroadcastGuardrail activation_guardrail_stats = 19;
 
+    message RestrictedMetricStats {
+      optional int64 restricted_metric_id = 1;
+      optional int64 insert_error = 2;
+      optional int64 table_creation_error = 3;
+      optional int64 table_deletion_error = 4;
+      repeated int64 flush_latency_ns = 5;
+      optional int64 category_changed_count = 6;
+    }
+
+    message RestrictedMetricQueryStats {
+      optional int32 calling_uid = 1;
+      optional int64 config_id = 2;
+      optional int32 config_uid = 3;
+      optional string config_package = 4;
+      optional InvalidQueryReason invalid_query_reason = 5;
+      optional int64 query_wall_time_ns = 6;
+      optional bool has_error = 7;
+      optional string query_error = 8;
+      optional int64 query_latency_ns = 9;
+    }
+    repeated RestrictedMetricQueryStats restricted_metric_query_stats = 20;
     optional uint32 shard_offset = 21;
+    optional int32 statsd_stats_id = 22;
 
     message SubscriptionStats {
       message PerSubscriptionStats {
@@ -617,6 +662,39 @@
     }
 
     optional SubscriptionStats subscription_stats = 23;
+
+    message SocketLossStats {
+      message LossStatsPerUid {
+        message AtomIdLossStats {
+            optional int32 atom_id = 1;
+            optional int32 error = 2;
+            optional int32 count = 3;
+        }
+        optional int32 uid = 1;
+        optional int64 first_timestamp_nanos = 2;
+        optional int64 last_timestamp_nanos = 3;
+        repeated AtomIdLossStats atom_id_loss_stats = 4;
+      }
+
+      repeated LossStatsPerUid loss_stats_per_uid = 1;
+
+      // tracks overflow of stats container on the libstatssocket side per logging application
+      message LossStatsOverflowCounters {
+        optional int32 uid = 1;
+        optional int32 count = 2;
+      }
+
+      repeated LossStatsOverflowCounters loss_stats_overflow_counters = 2;
+    }
+
+    optional SocketLossStats socket_loss_stats = 24;
+
+    message EventQueueStats {
+        optional int32 max_size_observed = 1;
+        optional int64 max_size_observed_elapsed_nanos = 2;
+    }
+
+    optional EventQueueStats event_queue_stats = 25;
 }
 
 message AlertTriggerDetails {
diff --git a/statsd/src/stats_log_util.cpp b/statsd/src/stats_log_util.cpp
index 1d9a43b..6d47344 100644
--- a/statsd/src/stats_log_util.cpp
+++ b/statsd/src/stats_log_util.cpp
@@ -598,6 +598,10 @@
     return nano / 1000000;
 }
 
+int64_t NanoToSeconds(const int64_t nano) {
+    return nano / NS_PER_SEC;
+}
+
 int64_t MillisToNano(const int64_t millis) {
     return millis * 1000000;
 }
@@ -617,7 +621,7 @@
     return success;
 }
 
-void mapIsolatedUidsToHostUidInLogEvent(const sp<UidMap> uidMap, LogEvent& event) {
+void mapIsolatedUidsToHostUidInLogEvent(const sp<UidMap>& uidMap, LogEvent& event) {
     uint8_t remainingUidCount = event.getNumUidFields();
     vector<FieldValue>* fieldValues = event.getMutableValues();
     auto it = fieldValues->begin();
diff --git a/statsd/src/stats_log_util.h b/statsd/src/stats_log_util.h
index 68155e4..16cdb35 100644
--- a/statsd/src/stats_log_util.h
+++ b/statsd/src/stats_log_util.h
@@ -76,10 +76,12 @@
 
 int64_t NanoToMillis(const int64_t nano);
 
+int64_t NanoToSeconds(const int64_t nano);
+
 int64_t MillisToNano(const int64_t millis);
 
 // Helper function to write a stats field to ProtoOutputStream if it's a non-zero value.
-void writeNonZeroStatToStream(const uint64_t fieldId, const int64_t value,
+void writeNonZeroStatToStream(const uint64_t fieldId, int64_t value,
                               ProtoOutputStream* protoOutput);
 
 // Helper function to write PulledAtomStats to ProtoOutputStream
@@ -117,7 +119,7 @@
     return atomId >= StatsdStats::kPullAtomStartTag && atomId < StatsdStats::kVendorAtomStartTag;
 }
 
-void mapIsolatedUidsToHostUidInLogEvent(const sp<UidMap> uidMap, LogEvent& event);
+void mapIsolatedUidsToHostUidInLogEvent(const sp<UidMap>& uidMap, LogEvent& event);
 
 std::string toHexString(const string& bytes);
 
diff --git a/statsd/src/stats_policy_config.proto b/statsd/src/stats_policy_config.proto
new file mode 100644
index 0000000..c1ea446
--- /dev/null
+++ b/statsd/src/stats_policy_config.proto
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.os.statsd;
+
+option java_package = "com.android.internal.os";
+option java_outer_classname = "StatsPolicyConfig";
+
+message StatsPolicyConfig {
+    optional int32 minimumClientsInAggregateResult = 1;
+}
\ No newline at end of file
diff --git a/statsd/src/stats_util.h b/statsd/src/stats_util.h
index fde1825..fabb7e3 100644
--- a/statsd/src/stats_util.h
+++ b/statsd/src/stats_util.h
@@ -59,6 +59,10 @@
     return isAtLeastU;
 }
 
+inline bool shouldKeepRandomSample(int samplingPercentage) {
+    return (rand() % (100) + 1) <= samplingPercentage;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/statsd_config.proto b/statsd/src/statsd_config.proto
index adb7235..8d48df0 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 {
@@ -229,6 +242,8 @@
 
   repeated MetricConditionLink links = 4;
 
+  optional int32 sampling_percentage = 5 [default = 100];
+
   reserved 100;
   reserved 101;
 }
@@ -258,6 +273,8 @@
 
   optional DimensionalSamplingInfo dimensional_sampling_info = 12;
 
+  optional int32 max_dimensions_per_bucket = 13;
+
   reserved 100;
   reserved 101;
 }
@@ -294,6 +311,8 @@
 
   optional DimensionalSamplingInfo dimensional_sampling_info = 13;
 
+  optional int32 max_dimensions_per_bucket = 14;
+
   reserved 100;
   reserved 101;
 }
@@ -335,6 +354,12 @@
 
   optional DimensionalSamplingInfo dimensional_sampling_info = 15;
 
+  optional int32 max_dimensions_per_bucket = 16;
+
+  optional int32 sampling_percentage = 17 [default = 100];
+
+  optional int32 pull_probability = 18 [default = 100];
+
   reserved 100;
   reserved 101;
 }
@@ -370,6 +395,8 @@
   }
   optional AggregationType aggregation_type = 8 [default = SUM];
 
+  repeated AggregationType aggregation_types = 25;
+
   optional bool include_sample_size = 22;
 
   optional int64 min_bucket_size_nanos = 10;
@@ -398,37 +425,41 @@
 
   optional DimensionalSamplingInfo dimensional_sampling_info = 23;
 
+  optional int32 max_dimensions_per_bucket = 24;
+
   reserved 100;
   reserved 101;
 }
 
 message KllMetric {
-    optional int64 id = 1;
+  optional int64 id = 1;
 
-    optional int64 what = 2;
+  optional int64 what = 2;
 
-    optional FieldMatcher kll_field = 3;
+  optional FieldMatcher kll_field = 3;
 
-    optional int64 condition = 4;
+  optional int64 condition = 4;
 
-    optional FieldMatcher dimensions_in_what = 5;
+  optional FieldMatcher dimensions_in_what = 5;
 
-    optional TimeUnit bucket = 6;
+  optional TimeUnit bucket = 6;
 
-    repeated MetricConditionLink links = 7;
+  repeated MetricConditionLink links = 7;
 
-    optional int64 min_bucket_size_nanos = 8;
+  optional int64 min_bucket_size_nanos = 8;
 
-    optional bool split_bucket_for_app_upgrade = 9;
+  optional bool split_bucket_for_app_upgrade = 9;
 
-    repeated int64 slice_by_state = 10;
+  repeated int64 slice_by_state = 10;
 
-    repeated MetricStateLink state_link = 11;
+  repeated MetricStateLink state_link = 11;
 
-    optional DimensionalSamplingInfo dimensional_sampling_info = 12;
+  optional DimensionalSamplingInfo dimensional_sampling_info = 12;
 
-    reserved 100;
-    reserved 101;
+  optional int32 max_dimensions_per_bucket = 13;
+
+  reserved 100;
+  reserved 101;
 }
 
 message Alert {
@@ -441,6 +472,8 @@
   optional int32 refractory_period_secs = 4;
 
   optional double trigger_if_sum_gt = 5;
+
+  optional float probability_of_informing = 6 [default = 1.1];
 }
 
 message Alarm {
@@ -449,6 +482,8 @@
   optional int64 offset_millis = 2;
 
   optional int64 period_millis = 3;
+
+  optional float probability_of_informing = 4 [default = 1.1];
 }
 
 message IncidentdDetails {
@@ -478,6 +513,15 @@
   optional bytes trace_config = 1;
 }
 
+message UprobestatsDetails {
+  // The |config| field is a proto-encoded message of type
+  // uprobestats.protos.UprobestatsConfig defined in
+  // //packages/modules/UprobeStats/src/config.proto. On device,
+  // statsd doesn't need to deserialize the message as it's just
+  // passed binary-encoded to the Uprobestats API.
+  optional bytes config = 1;
+}
+
 message BroadcastSubscriberDetails {
   optional int64 subscriber_id = 1;
   repeated string cookie = 2;
@@ -499,6 +543,7 @@
     IncidentdDetails incidentd_details = 4;
     PerfettoDetails perfetto_details = 5;
     BroadcastSubscriberDetails broadcast_subscriber_details = 6;
+    UprobestatsDetails uprobestats_details = 9;
   }
 
   optional float probability_of_informing = 7 [default = 1.1];
@@ -591,8 +636,18 @@
 
   optional uint32 package_certificate_hash_size_bytes = 26;
 
+  optional string restricted_metrics_delegate_package_name = 27;
+
   optional int32 max_metrics_memory_kb = 28;
 
+  optional int32 soft_metrics_memory_kb = 29;
+
+  message StatsdConfigOptions {
+    optional bool use_v2_soft_memory_limit = 1;
+  }
+
+  optional StatsdConfigOptions statsd_config_options = 30;
+
   // Do not use.
   reserved 1000, 1001;
 }
diff --git a/statsd/src/statsd_metadata.proto b/statsd/src/statsd_metadata.proto
index 200b392..7d18894 100644
--- a/statsd/src/statsd_metadata.proto
+++ b/statsd/src/statsd_metadata.proto
@@ -56,12 +56,18 @@
   repeated AlertDimensionKeyedData alert_dim_keyed_data = 2;
 }
 
+message MetricMetadata {
+  optional int64 metric_id = 1;
+  optional int32 restricted_category = 2;
+}
+
 // All metadata for a config in statsd
 message StatsMetadata {
   optional ConfigKey config_key = 1;
   repeated AlertMetadata alert_metadata = 2;
+  repeated MetricMetadata metric_metadata = 3;
 }
 
 message StatsMetadataList {
   repeated StatsMetadata stats_metadata = 1;
-}
\ No newline at end of file
+}
diff --git a/statsd/src/storage/StorageManager.cpp b/statsd/src/storage/StorageManager.cpp
index 1c669eb..7ca88fe 100644
--- a/statsd/src/storage/StorageManager.cpp
+++ b/statsd/src/storage/StorageManager.cpp
@@ -17,15 +17,19 @@
 #define STATSD_DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
-#include "android-base/stringprintf.h"
-#include "guardrail/StatsdStats.h"
 #include "storage/StorageManager.h"
-#include "stats_log_util.h"
 
 #include <android-base/file.h>
 #include <private/android_filesystem_config.h>
+#include <sys/stat.h>
+
 #include <fstream>
 
+#include "android-base/stringprintf.h"
+#include "guardrail/StatsdStats.h"
+#include "stats_log_util.h"
+#include "utils/DbUtils.h"
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -118,6 +122,16 @@
     output->mIsHistory = (substr != nullptr && strcmp("history", substr) == 0);
 }
 
+// Returns array of int64_t which contains a sqlite db's uid and configId
+static ConfigKey parseDbName(char* name) {
+    char* uid = strtok(name, "_");
+    char* configId = strtok(nullptr, ".");
+    if (uid == nullptr || configId == nullptr) {
+        return ConfigKey(-1, -1);
+    }
+    return ConfigKey(StrToInt64(uid), StrToInt64(configId));
+}
+
 void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) {
     int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
     if (fd == -1) {
@@ -807,6 +821,55 @@
             totalFileSize);
 }
 
+void StorageManager::enforceDbGuardrails(const char* path, const int64_t currWallClockSec,
+                                         const int64_t maxBytes) {
+    if (!isAtLeastU()) {
+        return;
+    }
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
+    if (dir == NULL) {
+        VLOG("Path %s does not exist", path);
+        return;
+    }
+
+    dirent* de;
+    int64_t deleteThresholdSec = currWallClockSec - StatsdStats::kMaxAgeSecond;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] == '.' || de->d_type == DT_DIR) continue;
+        string fullPathName = StringPrintf("%s/%s", path, name);
+        struct stat fileInfo;
+        const ConfigKey key = parseDbName(name);
+        if (stat(fullPathName.c_str(), &fileInfo) != 0) {
+            StatsdStats::getInstance().noteDbStatFailed(key);
+            // Remove file if stat fails.
+            remove(fullPathName.c_str());
+            continue;
+        }
+        StatsdStats::getInstance().noteRestrictedConfigDbSize(key, currWallClockSec,
+                                                              fileInfo.st_size);
+        if (fileInfo.st_mtime <= deleteThresholdSec) {
+            StatsdStats::getInstance().noteDbTooOld(key);
+            remove(fullPathName.c_str());
+        }
+        if (fileInfo.st_size >= maxBytes) {
+            StatsdStats::getInstance().noteDbSizeExceeded(key);
+            remove(fullPathName.c_str());
+        }
+        if (hasFile(dbutils::getDbName(key).c_str())) {
+            dbutils::verifyIntegrityAndDeleteIfNecessary(key);
+        } else {
+            // Remove file if the file name fails to parse.
+            remove(fullPathName.c_str());
+        }
+    }
+}
+
+bool StorageManager::hasFile(const char* file) {
+    struct stat fileInfo;
+    return stat(file, &fileInfo) == 0;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/storage/StorageManager.h b/statsd/src/storage/StorageManager.h
index b0840ae..0a459a8 100644
--- a/statsd/src/storage/StorageManager.h
+++ b/statsd/src/storage/StorageManager.h
@@ -42,7 +42,7 @@
 class StorageManager : public virtual RefBase {
 public:
     struct FileInfo {
-        FileInfo(std::string name, bool isHistory, int fileSize, long fileAge)
+        FileInfo(const std::string& name, bool isHistory, int fileSize, long fileAge)
             : mFileName(name),
               mIsHistory(isHistory),
               mFileSizeBytes(fileSize),
@@ -162,6 +162,10 @@
 
     static void sortFiles(vector<FileInfo>* fileNames);
 
+    static void enforceDbGuardrails(const char* path, int64_t wallClockSec, int64_t maxBytes);
+
+    static bool hasFile(const char* file);
+
 private:
     /**
      * Prints disk usage statistics about a directory related to statsd.
diff --git a/statsd/src/utils/DbUtils.cpp b/statsd/src/utils/DbUtils.cpp
new file mode 100644
index 0000000..35ad150
--- /dev/null
+++ b/statsd/src/utils/DbUtils.cpp
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define STATSD_DEBUG false  // STOPSHIP if true
+
+#include "Log.h"
+
+#include "utils/DbUtils.h"
+
+#include <android/api-level.h>
+
+#include "FieldValue.h"
+#include "android-base/properties.h"
+#include "android-base/stringprintf.h"
+#include "stats_log_util.h"
+#include "storage/StorageManager.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+namespace dbutils {
+
+using ::android::os::statsd::FLOAT;
+using ::android::os::statsd::INT;
+using ::android::os::statsd::LONG;
+using ::android::os::statsd::StorageManager;
+using ::android::os::statsd::STRING;
+using base::GetProperty;
+using base::StringPrintf;
+
+const string TABLE_NAME_PREFIX = "metric_";
+const string COLUMN_NAME_ATOM_TAG = "atomId";
+const string COLUMN_NAME_EVENT_ELAPSED_CLOCK_NS = "elapsedTimestampNs";
+const string COLUMN_NAME_EVENT_WALL_CLOCK_NS = "wallTimestampNs";
+
+const string COLUMN_NAME_SDK_VERSION = "sdkVersion";
+const string COLUMN_NAME_MODEL = "model";
+const string COLUMN_NAME_PRODUCT = "product";
+const string COLUMN_NAME_HARDWARE = "hardware";
+const string COLUMN_NAME_DEVICE = "device";
+const string COLUMN_NAME_BUILD = "osBuild";
+const string COLUMN_NAME_FINGERPRINT = "fingerprint";
+const string COLUMN_NAME_BRAND = "brand";
+const string COLUMN_NAME_MANUFACTURER = "manufacturer";
+const string COLUMN_NAME_BOARD = "board";
+
+static std::vector<std::string> getExpectedTableSchema(const LogEvent& logEvent) {
+    vector<std::string> result;
+    for (const FieldValue& fieldValue : logEvent.getValues()) {
+        if (fieldValue.mField.getDepth() > 0) {
+            // Repeated fields are not supported.
+            continue;
+        }
+        switch (fieldValue.mValue.getType()) {
+            case INT:
+            case LONG:
+                result.push_back("INTEGER");
+                break;
+            case STRING:
+                result.push_back("TEXT");
+                break;
+            case FLOAT:
+                result.push_back("REAL");
+                break;
+            default:
+                // Byte array fields are not supported.
+                break;
+        }
+    }
+    return result;
+}
+
+static int integrityCheckCallback(void*, int colCount, char** queryResults, char**) {
+    if (colCount == 0 || strcmp(queryResults[0], "ok") != 0) {
+        // Returning 1 is an error code that causes exec to stop and error.
+        return 1;
+    }
+    return 0;
+}
+
+string getDbName(const ConfigKey& key) {
+    return StringPrintf("%s/%d_%lld.db", STATS_RESTRICTED_DATA_DIR, key.GetUid(),
+                        (long long)key.GetId());
+}
+
+static string getCreateSqlString(const int64_t metricId, const LogEvent& event) {
+    string result = StringPrintf("CREATE TABLE IF NOT EXISTS %s%s", TABLE_NAME_PREFIX.c_str(),
+                                 reformatMetricId(metricId).c_str());
+    result += StringPrintf("(%s INTEGER,%s INTEGER,%s INTEGER,", COLUMN_NAME_ATOM_TAG.c_str(),
+                           COLUMN_NAME_EVENT_ELAPSED_CLOCK_NS.c_str(),
+                           COLUMN_NAME_EVENT_WALL_CLOCK_NS.c_str());
+    for (size_t fieldId = 1; fieldId <= event.getValues().size(); ++fieldId) {
+        const FieldValue& fieldValue = event.getValues()[fieldId - 1];
+        if (fieldValue.mField.getDepth() > 0) {
+            // Repeated fields are not supported.
+            continue;
+        }
+        switch (fieldValue.mValue.getType()) {
+            case INT:
+            case LONG:
+                result += StringPrintf("field_%d INTEGER,", fieldValue.mField.getPosAtDepth(0));
+                break;
+            case STRING:
+                result += StringPrintf("field_%d TEXT,", fieldValue.mField.getPosAtDepth(0));
+                break;
+            case FLOAT:
+                result += StringPrintf("field_%d REAL,", fieldValue.mField.getPosAtDepth(0));
+                break;
+            default:
+                // Byte array fields are not supported.
+                break;
+        }
+    }
+    result.pop_back();
+    result += ") STRICT;";
+    return result;
+}
+
+string reformatMetricId(const int64_t metricId) {
+    return metricId < 0 ? StringPrintf("n%lld", (long long)metricId * -1)
+                        : StringPrintf("%lld", (long long)metricId);
+}
+
+bool createTableIfNeeded(const ConfigKey& key, const int64_t metricId, const LogEvent& event) {
+    const string dbName = getDbName(key);
+    sqlite3* db;
+    if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+        sqlite3_close(db);
+        return false;
+    }
+
+    char* error = nullptr;
+    string zSql = getCreateSqlString(metricId, event);
+    sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error);
+    sqlite3_close(db);
+    if (error) {
+        ALOGW("Failed to create table to db: %s", error);
+        return false;
+    }
+    return true;
+}
+
+bool isEventCompatible(const ConfigKey& key, const int64_t metricId, const LogEvent& event) {
+    const string dbName = getDbName(key);
+    sqlite3* db;
+    if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+        sqlite3_close(db);
+        return false;
+    }
+    string zSql = StringPrintf("PRAGMA table_info(metric_%s);", reformatMetricId(metricId).c_str());
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    if (!query(key, zSql, rows, columnTypes, columnNames, err)) {
+        ALOGE("Failed to check table schema for metric %lld: %s", (long long)metricId, err.c_str());
+        sqlite3_close(db);
+        return false;
+    }
+    // Sample query result
+    // cid  name               type     notnull  dflt_value  pk
+    // ---  -----------------  -------  -------  ----------  --
+    // 0    atomId             INTEGER  0        (null)      0
+    // 1    elapsedTimestampNs INTEGER  0        (null)      0
+    // 2    wallTimestampNs    INTEGER  0        (null)      0
+    // 3    field_1            INTEGER  0        (null)      0
+    // 4    field_2            TEXT     0        (null)      0
+    std::vector<string> tableSchema;
+    for (size_t i = 3; i < rows.size(); ++i) {  // Atom fields start at the third row
+        tableSchema.push_back(rows[i][2]);  // The third column stores the data type for the column
+    }
+    sqlite3_close(db);
+    // An empty rows vector implies the table has not yet been created.
+    return rows.size() == 0 || getExpectedTableSchema(event) == tableSchema;
+}
+
+bool deleteTable(const ConfigKey& key, const int64_t metricId) {
+    const string dbName = getDbName(key);
+    sqlite3* db;
+    if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+        sqlite3_close(db);
+        return false;
+    }
+    string zSql = StringPrintf("DROP TABLE metric_%s", reformatMetricId(metricId).c_str());
+    char* error = nullptr;
+    sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error);
+    sqlite3_close(db);
+    if (error) {
+        ALOGW("Failed to drop table from db: %s", error);
+        return false;
+    }
+    return true;
+}
+
+void deleteDb(const ConfigKey& key) {
+    const string dbName = getDbName(key);
+    StorageManager::deleteFile(dbName.c_str());
+}
+
+sqlite3* getDb(const ConfigKey& key) {
+    const string dbName = getDbName(key);
+    sqlite3* db;
+    if (sqlite3_open(dbName.c_str(), &db) == SQLITE_OK) {
+        return db;
+    }
+    return nullptr;
+}
+
+void closeDb(sqlite3* db) {
+    sqlite3_close(db);
+}
+
+static bool getInsertSqlStmt(sqlite3* db, sqlite3_stmt** stmt, const int64_t metricId,
+                             const vector<LogEvent>& events, string& err) {
+    string result =
+            StringPrintf("INSERT INTO metric_%s VALUES", reformatMetricId(metricId).c_str());
+    for (auto& logEvent : events) {
+        result += StringPrintf("(%d, %lld, %lld,", logEvent.GetTagId(),
+                               (long long)logEvent.GetElapsedTimestampNs(),
+                               (long long)logEvent.GetLogdTimestampNs());
+        for (auto& fieldValue : logEvent.getValues()) {
+            if (fieldValue.mField.getDepth() > 0 || fieldValue.mValue.getType() == STORAGE) {
+                // Repeated fields and byte fields are not supported.
+                continue;
+            }
+            result += "?,";
+        }
+        result.pop_back();
+        result += "),";
+    }
+    result.pop_back();
+    result += ";";
+    if (sqlite3_prepare_v2(db, result.c_str(), -1, stmt, nullptr) != SQLITE_OK) {
+        err = sqlite3_errmsg(db);
+        return false;
+    }
+    // ? parameters start with an index of 1 from start of query string to the
+    // end.
+    int32_t index = 1;
+    for (auto& logEvent : events) {
+        for (auto& fieldValue : logEvent.getValues()) {
+            if (fieldValue.mField.getDepth() > 0 || fieldValue.mValue.getType() == STORAGE) {
+                // Repeated fields and byte fields are not supported.
+                continue;
+            }
+            switch (fieldValue.mValue.getType()) {
+                case INT:
+                    sqlite3_bind_int(*stmt, index, fieldValue.mValue.int_value);
+                    break;
+                case LONG:
+                    sqlite3_bind_int64(*stmt, index, fieldValue.mValue.long_value);
+                    break;
+                case STRING:
+                    sqlite3_bind_text(*stmt, index, fieldValue.mValue.str_value.c_str(), -1,
+                                      SQLITE_STATIC);
+                    break;
+                case FLOAT:
+                    sqlite3_bind_double(*stmt, index, fieldValue.mValue.float_value);
+                    break;
+                default:
+                    // Byte array fields are not supported.
+                    break;
+            }
+            ++index;
+        }
+    }
+    return true;
+}
+
+bool insert(const ConfigKey& key, const int64_t metricId, const vector<LogEvent>& events,
+            string& error) {
+    const string dbName = getDbName(key);
+    sqlite3* db;
+    if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+        error = sqlite3_errmsg(db);
+        sqlite3_close(db);
+        return false;
+    }
+    bool success = insert(db, metricId, events, error);
+    sqlite3_close(db);
+    return success;
+}
+
+bool insert(sqlite3* db, const int64_t metricId, const vector<LogEvent>& events, string& error) {
+    sqlite3_stmt* stmt = nullptr;
+    if (!getInsertSqlStmt(db, &stmt, metricId, events, error)) {
+        ALOGW("Failed to generate prepared sql insert query %s", error.c_str());
+        sqlite3_finalize(stmt);
+        return false;
+    }
+    if (sqlite3_step(stmt) != SQLITE_DONE) {
+        error = sqlite3_errmsg(db);
+        ALOGW("Failed to insert data to db: %s", error.c_str());
+        sqlite3_finalize(stmt);
+        return false;
+    }
+    sqlite3_finalize(stmt);
+    return true;
+}
+
+bool query(const ConfigKey& key, const string& zSql, vector<vector<string>>& rows,
+           vector<int32_t>& columnTypes, vector<string>& columnNames, string& err) {
+    const string dbName = getDbName(key);
+    sqlite3* db;
+    if (sqlite3_open_v2(dbName.c_str(), &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) {
+        err = sqlite3_errmsg(db);
+        sqlite3_close(db);
+        return false;
+    }
+    sqlite3_stmt* stmt;
+    if (sqlite3_prepare_v2(db, zSql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
+        err = sqlite3_errmsg(db);
+        sqlite3_finalize(stmt);
+        sqlite3_close(db);
+        return false;
+    }
+    int result = sqlite3_step(stmt);
+    bool firstIter = true;
+    while (result == SQLITE_ROW) {
+        int colCount = sqlite3_column_count(stmt);
+        vector<string> rowData(colCount);
+        for (int i = 0; i < colCount; ++i) {
+            if (firstIter) {
+                int32_t columnType = sqlite3_column_type(stmt, i);
+                // Needed to convert to java compatible cursor types. See AbstractCursor#getType()
+                if (columnType == 5) {
+                    columnType = 0;  // Remap 5 (null type) to 0 for java cursor
+                }
+                columnTypes.push_back(columnType);
+                columnNames.push_back(reinterpret_cast<const char*>(sqlite3_column_name(stmt, i)));
+            }
+            const unsigned char* textResult = sqlite3_column_text(stmt, i);
+            string colData =
+                    textResult != nullptr ? string(reinterpret_cast<const char*>(textResult)) : "";
+            rowData[i] = std::move(colData);
+        }
+        rows.push_back(std::move(rowData));
+        firstIter = false;
+        result = sqlite3_step(stmt);
+    }
+    sqlite3_finalize(stmt);
+    if (result != SQLITE_DONE) {
+        err = sqlite3_errmsg(db);
+        sqlite3_close(db);
+        return false;
+    }
+    sqlite3_close(db);
+    return true;
+}
+
+bool flushTtl(sqlite3* db, const int64_t metricId, const int64_t ttlWallClockNs) {
+    string zSql = StringPrintf("DELETE FROM %s%s WHERE %s <= %lld", TABLE_NAME_PREFIX.c_str(),
+                               reformatMetricId(metricId).c_str(),
+                               COLUMN_NAME_EVENT_WALL_CLOCK_NS.c_str(), (long long)ttlWallClockNs);
+
+    char* error = nullptr;
+    sqlite3_exec(db, zSql.c_str(), nullptr, nullptr, &error);
+    if (error) {
+        ALOGW("Failed to enforce ttl: %s", error);
+        return false;
+    }
+    return true;
+}
+
+void verifyIntegrityAndDeleteIfNecessary(const ConfigKey& configKey) {
+    const string dbName = getDbName(configKey);
+    sqlite3* db;
+    if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+        sqlite3_close(db);
+        return;
+    }
+    string zSql = "PRAGMA integrity_check";
+
+    char* error = nullptr;
+    sqlite3_exec(db, zSql.c_str(), integrityCheckCallback, nullptr, &error);
+    if (error) {
+        StatsdStats::getInstance().noteDbCorrupted(configKey);
+        ALOGW("Integrity Check failed %s", error);
+        sqlite3_close(db);
+        deleteDb(configKey);
+        return;
+    }
+    sqlite3_close(db);
+}
+
+static bool getDeviceInfoInsertStmt(sqlite3* db, sqlite3_stmt** stmt, string error) {
+    string insertSql = StringPrintf("INSERT INTO device_info VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+    if (sqlite3_prepare_v2(db, insertSql.c_str(), -1, stmt, nullptr) != SQLITE_OK) {
+        error = sqlite3_errmsg(db);
+        return false;
+    }
+
+    // ? parameters start with an index of 1 from start of query string to the end.
+    int32_t index = 1;
+
+    int32_t sdkVersion = android_get_device_api_level();
+    sqlite3_bind_int(*stmt, index, sdkVersion);
+    ++index;
+
+    string model = GetProperty("ro.product.model", "(unknown)");
+    sqlite3_bind_text(*stmt, index, model.c_str(), -1, SQLITE_TRANSIENT);
+    ++index;
+
+    string product = GetProperty("ro.product.name", "(unknown)");
+    sqlite3_bind_text(*stmt, index, product.c_str(), -1, SQLITE_TRANSIENT);
+    ++index;
+
+    string hardware = GetProperty("ro.hardware", "(unknown)");
+    sqlite3_bind_text(*stmt, index, hardware.c_str(), -1, SQLITE_TRANSIENT);
+    ++index;
+
+    string device = GetProperty("ro.product.device", "(unknown)");
+    sqlite3_bind_text(*stmt, index, device.c_str(), -1, SQLITE_TRANSIENT);
+    ++index;
+
+    string osBuild = GetProperty("ro.build.id", "(unknown)");
+    sqlite3_bind_text(*stmt, index, osBuild.c_str(), -1, SQLITE_TRANSIENT);
+    ++index;
+
+    string fingerprint = GetProperty("ro.build.fingerprint", "(unknown)");
+    sqlite3_bind_text(*stmt, index, fingerprint.c_str(), -1, SQLITE_TRANSIENT);
+    ++index;
+
+    string brand = GetProperty("ro.product.brand", "(unknown)");
+    sqlite3_bind_text(*stmt, index, brand.c_str(), -1, SQLITE_TRANSIENT);
+    ++index;
+
+    string manufacturer = GetProperty("ro.product.manufacturer", "(unknown)");
+    sqlite3_bind_text(*stmt, index, manufacturer.c_str(), -1, SQLITE_TRANSIENT);
+    ++index;
+
+    string board = GetProperty("ro.product.board", "(unknown)");
+    sqlite3_bind_text(*stmt, index, board.c_str(), -1, SQLITE_TRANSIENT);
+    ++index;
+
+    return true;
+}
+
+bool updateDeviceInfoTable(const ConfigKey& key, string& error) {
+    const string dbName = getDbName(key);
+    sqlite3* db;
+    if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
+        error = sqlite3_errmsg(db);
+        sqlite3_close(db);
+        return false;
+    }
+
+    string dropTableSql = "DROP TABLE device_info";
+    // Ignore possible error result code if table has not yet been created.
+    sqlite3_exec(db, dropTableSql.c_str(), nullptr, nullptr, nullptr);
+
+    string createTableSql = StringPrintf(
+            "CREATE TABLE device_info(%s INTEGER, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s TEXT, %s "
+            "TEXT, %s TEXT, %s TEXT, %s TEXT) "
+            "STRICT",
+            COLUMN_NAME_SDK_VERSION.c_str(), COLUMN_NAME_MODEL.c_str(), COLUMN_NAME_PRODUCT.c_str(),
+            COLUMN_NAME_HARDWARE.c_str(), COLUMN_NAME_DEVICE.c_str(), COLUMN_NAME_BUILD.c_str(),
+            COLUMN_NAME_FINGERPRINT.c_str(), COLUMN_NAME_BRAND.c_str(),
+            COLUMN_NAME_MANUFACTURER.c_str(), COLUMN_NAME_BOARD.c_str());
+    if (sqlite3_exec(db, createTableSql.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK) {
+        error = sqlite3_errmsg(db);
+        ALOGW("Failed to create device info table %s", error.c_str());
+        sqlite3_close(db);
+        return false;
+    }
+
+    sqlite3_stmt* stmt = nullptr;
+    if (!getDeviceInfoInsertStmt(db, &stmt, error)) {
+        ALOGW("Failed to generate device info prepared sql insert query %s", error.c_str());
+        sqlite3_finalize(stmt);
+        sqlite3_close(db);
+        return false;
+    }
+
+    if (sqlite3_step(stmt) != SQLITE_DONE) {
+        error = sqlite3_errmsg(db);
+        ALOGW("Failed to insert data to device info table: %s", error.c_str());
+        sqlite3_finalize(stmt);
+        sqlite3_close(db);
+        return false;
+    }
+    sqlite3_finalize(stmt);
+    sqlite3_close(db);
+    return true;
+}
+}  // namespace dbutils
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/utils/DbUtils.h b/statsd/src/utils/DbUtils.h
new file mode 100644
index 0000000..06d1292
--- /dev/null
+++ b/statsd/src/utils/DbUtils.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <sqlite3.h>
+
+#include "config/ConfigKey.h"
+#include "logd/LogEvent.h"
+
+using std::string;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+namespace dbutils {
+
+#define STATS_RESTRICTED_DATA_DIR "/data/misc/stats-data/restricted-data"
+
+inline int32_t getDbVersion() {
+    return SQLITE_VERSION_NUMBER;
+};
+
+string getDbName(const ConfigKey& key);
+
+string reformatMetricId(const int64_t metricId);
+
+/* Creates a new data table for a specified metric if one does not yet exist. */
+bool createTableIfNeeded(const ConfigKey& key, int64_t metricId, const LogEvent& event);
+
+/* Checks whether the table schema for the given metric matches the event.
+ * Returns true if the table has not yet been created.
+ */
+bool isEventCompatible(const ConfigKey& key, int64_t metricId, const LogEvent& event);
+
+/* Deletes a data table for the specified metric. */
+bool deleteTable(const ConfigKey& key, int64_t metricId);
+
+/* Deletes the SQLite db data file. */
+void deleteDb(const ConfigKey& key);
+
+/* Gets a handle to the sqlite db. You must call closeDb to free the allocated memory.
+ * Returns a nullptr if an error occurs.
+ */
+sqlite3* getDb(const ConfigKey& key);
+
+/* Closes the handle to the sqlite db. */
+void closeDb(sqlite3* db);
+
+/* Inserts new data into the specified metric data table.
+ * A temp sqlite handle is created using the ConfigKey.
+ */
+bool insert(const ConfigKey& key, int64_t metricId, const vector<LogEvent>& events, string& error);
+
+/* Inserts new data into the specified sqlite db handle. */
+bool insert(sqlite3* db, int64_t metricId, const vector<LogEvent>& events, string& error);
+
+/* Executes a sql query on the specified SQLite db.
+ * A temp sqlite handle is created using the ConfigKey.
+ */
+bool query(const ConfigKey& key, const string& zSql, vector<vector<string>>& rows,
+           vector<int32_t>& columnTypes, vector<string>& columnNames, string& err);
+
+bool flushTtl(sqlite3* db, int64_t metricId, int64_t ttlWallClockNs);
+
+/* Checks for database corruption and deletes the db if it is corrupted. */
+void verifyIntegrityAndDeleteIfNecessary(const ConfigKey& key);
+
+/* Creates and updates the device info table for the given configKey. */
+bool updateDeviceInfoTable(const ConfigKey& key, string& error);
+
+}  // namespace dbutils
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/utils/MultiConditionTrigger.cpp b/statsd/src/utils/MultiConditionTrigger.cpp
index 5ef50ee..d9ad25b 100644
--- a/statsd/src/utils/MultiConditionTrigger.cpp
+++ b/statsd/src/utils/MultiConditionTrigger.cpp
@@ -19,19 +19,21 @@
 
 #include <thread>
 
-using namespace std;
-
 namespace android {
 namespace os {
 namespace statsd {
 
+using std::function;
+using std::set;
+using std::string;
+
 MultiConditionTrigger::MultiConditionTrigger(const set<string>& conditionNames,
                                              function<void()> trigger)
     : mRemainingConditionNames(conditionNames),
-      mTrigger(trigger),
+      mTrigger(std::move(trigger)),
       mCompleted(mRemainingConditionNames.empty()) {
     if (mCompleted) {
-        thread executorThread([this] { mTrigger(); });
+        std::thread executorThread([this] { mTrigger(); });
         executorThread.detach();
     }
 }
@@ -39,7 +41,7 @@
 void MultiConditionTrigger::markComplete(const string& conditionName) {
     bool doTrigger = false;
     {
-        lock_guard<mutex> lg(mMutex);
+        std::lock_guard<std::mutex> lg(mMutex);
         if (mCompleted) {
             return;
         }
diff --git a/statsd/src/utils/MultiConditionTrigger.h b/statsd/src/utils/MultiConditionTrigger.h
index 51f6029..7a09119 100644
--- a/statsd/src/utils/MultiConditionTrigger.h
+++ b/statsd/src/utils/MultiConditionTrigger.h
@@ -40,16 +40,15 @@
 
     // Mark a specific condition as true. If this condition has called markComplete already or if
     // the event was not specified in the constructor, the function is a no-op.
-    void markComplete(const std::string& eventName);
+    void markComplete(const std::string& conditionName);
 
 private:
     mutable std::mutex mMutex;
     std::set<std::string> mRemainingConditionNames;
     std::function<void()> mTrigger;
     bool mCompleted;
-
-    FRIEND_TEST(MultiConditionTriggerTest, TestCountDownCalledBySameEventName);
 };
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/utils/Regex.cpp b/statsd/src/utils/Regex.cpp
new file mode 100644
index 0000000..7c24dc8
--- /dev/null
+++ b/statsd/src/utils/Regex.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 "Regex.h"
+
+#include <log/log.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::string;
+using std::unique_ptr;
+
+Regex::Regex(regex_t impl) : mImpl(std::move(impl)) {
+}
+
+Regex::~Regex() {
+    regfree(&mImpl);
+}
+
+unique_ptr<Regex> Regex::create(const string& pattern) {
+    regex_t impl;
+    int status = regcomp(&impl, pattern.c_str(), REG_EXTENDED);
+
+    if (status != 0) {  // Invalid regex pattern.
+        // Calculate size of string needed to store error description.
+        size_t errBufSize = regerror(status, &impl, nullptr, 0);
+
+        // Get the error description.
+        char errBuf[errBufSize];
+        regerror(status, &impl, errBuf, errBufSize);
+
+        ALOGE("regex_error: %s, pattern: %s", errBuf, pattern.c_str());
+        regfree(&impl);
+        return nullptr;
+    } else if (impl.re_nsub > 0) {
+        ALOGE("regex_error: subexpressions are not allowed, pattern: %s", pattern.c_str());
+        regfree(&impl);
+        return nullptr;
+    } else {
+        return std::make_unique<Regex>(impl);
+    }
+}
+
+bool Regex::replace(string& str, const string& replacement) {
+    regmatch_t match;
+    int status = regexec(&mImpl, str.c_str(), 1 /* nmatch */, &match /* pmatch */, 0 /* flags */);
+
+    if (status != 0 || match.rm_so == -1) {  // No match.
+        return false;
+    }
+    str.replace(match.rm_so, match.rm_eo - match.rm_so, replacement);
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/utils/Regex.h b/statsd/src/utils/Regex.h
new file mode 100644
index 0000000..f1931fa
--- /dev/null
+++ b/statsd/src/utils/Regex.h
@@ -0,0 +1,48 @@
+/*
+ * 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.h>
+
+#include <memory>
+#include <string>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class Regex {
+public:
+    Regex(regex_t impl);  // Do not use. It is public for std::make_unique. Use Regex::create.
+    ~Regex();
+    Regex& operator=(const Regex&) = delete;
+    Regex(const Regex&) = delete;
+
+    // Returns nullptr if pattern is not valid POSIX regex.
+    static std::unique_ptr<Regex> create(const std::string& pattern);
+
+    // Looks for a regex match in str and replaces the matched portion with replacement in-place.
+    // Returns true if there was a match, false otherwise.
+    bool replace(std::string& str, const std::string& replacement);
+
+private:
+    regex_t mImpl;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/utils/RestrictedPolicyManager.cpp b/statsd/src/utils/RestrictedPolicyManager.cpp
new file mode 100644
index 0000000..0c18a13
--- /dev/null
+++ b/statsd/src/utils/RestrictedPolicyManager.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/RestrictedPolicyManager.h"
+
+#include "stats_annotations.h"
+
+#define STATSD_DEBUG false  // STOPSHIP if true
+
+namespace android {
+namespace os {
+namespace statsd {
+
+RestrictedPolicyManager& RestrictedPolicyManager::getInstance() {
+    static RestrictedPolicyManager policyInstance;
+    return policyInstance;
+}
+
+int32_t RestrictedPolicyManager::getRestrictedCategoryTtl(
+        const StatsdRestrictionCategory categoryId) const {
+    return mRestrictionCategoryTtlInDaysMap.find(categoryId) !=
+                           mRestrictionCategoryTtlInDaysMap.end()
+                   ? mRestrictionCategoryTtlInDaysMap.at(categoryId)
+                   : DEFAULT_RESTRICTED_CATEGORY_TTL_DAYS;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/utils/RestrictedPolicyManager.h b/statsd/src/utils/RestrictedPolicyManager.h
new file mode 100644
index 0000000..9795640
--- /dev/null
+++ b/statsd/src/utils/RestrictedPolicyManager.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <stdlib.h>
+
+#include <map>
+
+#include "stats_annotations.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#define DEFAULT_RESTRICTED_CATEGORY_TTL_DAYS 7
+
+// Restricted categories used internally by statsd.
+enum StatsdRestrictionCategory : int32_t {
+    CATEGORY_UNKNOWN = -1,
+    CATEGORY_NO_RESTRICTION = 0,
+    CATEGORY_DIAGNOSTIC = ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC,
+    CATEGORY_SYSTEM_INTELLIGENCE = ASTATSLOG_RESTRICTION_CATEGORY_SYSTEM_INTELLIGENCE,
+    CATEGORY_AUTHENTICATION = ASTATSLOG_RESTRICTION_CATEGORY_AUTHENTICATION,
+    CATEGORY_FRAUD_AND_ABUSE = ASTATSLOG_RESTRICTION_CATEGORY_FRAUD_AND_ABUSE,
+};
+
+// Single instance shared across the process.
+class RestrictedPolicyManager {
+    RestrictedPolicyManager() = default;
+
+public:
+    static RestrictedPolicyManager& getInstance();
+
+    // Gets the TTL in days for a particular restricted category. Returns the default for unknown
+    // categories.
+    int32_t getRestrictedCategoryTtl(StatsdRestrictionCategory categoryId) const;
+
+private:
+    std::map<StatsdRestrictionCategory, int32_t> mRestrictionCategoryTtlInDaysMap;
+};
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/utils/ShardOffsetProvider.cpp b/statsd/src/utils/ShardOffsetProvider.cpp
index 4a0df97..8894b4d 100644
--- a/statsd/src/utils/ShardOffsetProvider.cpp
+++ b/statsd/src/utils/ShardOffsetProvider.cpp
@@ -16,31 +16,15 @@
 
 #include "ShardOffsetProvider.h"
 
-#include <errno.h>
-#include <sys/random.h>
-#include <unistd.h>
-
-#include <chrono>
-
 namespace android {
 namespace os {
 namespace statsd {
 
-ShardOffsetProvider::ShardOffsetProvider() {
-    unsigned int seed = 0;
-    // getrandom() reads bytes from urandom source into buf. If getrandom()
-    // is unable to read from urandom source, then it returns -1 and we set
-    // our seed to be time(nullptr) as a fallback.
-    if (TEMP_FAILURE_RETRY(
-                getrandom(static_cast<void*>(&seed), sizeof(unsigned int), GRND_NONBLOCK)) < 0) {
-        seed = time(nullptr);
-    }
-    srand(seed);
-    mShardOffset = rand();
+ShardOffsetProvider::ShardOffsetProvider(const uint32_t shardOffset) : mShardOffset(shardOffset) {
 }
 
 ShardOffsetProvider& ShardOffsetProvider::getInstance() {
-    static ShardOffsetProvider sShardOffsetProvider;
+    static ShardOffsetProvider sShardOffsetProvider(rand());
     return sShardOffsetProvider;
 }
 
diff --git a/statsd/src/utils/ShardOffsetProvider.h b/statsd/src/utils/ShardOffsetProvider.h
index dd6a5a7..506e230 100644
--- a/statsd/src/utils/ShardOffsetProvider.h
+++ b/statsd/src/utils/ShardOffsetProvider.h
@@ -39,7 +39,7 @@
     static ShardOffsetProvider& getInstance();
 
 private:
-    ShardOffsetProvider();
+    ShardOffsetProvider(const uint32_t shardOffset);
 
     // Only used for testing.
     void setShardOffset(const uint32_t shardOffset) {
@@ -51,6 +51,7 @@
     FRIEND_TEST(CountMetricE2eTest, TestDimensionalSampling);
     FRIEND_TEST(DurationMetricE2eTest, TestDimensionalSampling);
     FRIEND_TEST(GaugeMetricE2ePushedTest, TestDimensionalSampling);
+    FRIEND_TEST(GaugeMetricE2ePushedTest, TestPushedGaugeMetricSamplingWithDimensionalSampling);
     FRIEND_TEST(GaugeMetricProducerTest, TestPullDimensionalSampling);
     FRIEND_TEST(KllMetricE2eTest, TestDimensionalSampling);
     FRIEND_TEST(NumericValueMetricProducerTest, TestDimensionalSampling);
diff --git a/statsd/src/utils/api_tracing.h b/statsd/src/utils/api_tracing.h
new file mode 100644
index 0000000..69edeae
--- /dev/null
+++ b/statsd/src/utils/api_tracing.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#define ATRACE_TAG ATRACE_TAG_APP
+
+#include <utils/Trace.h>
diff --git a/statsd/tests/ConfigManager_test.cpp b/statsd/tests/ConfigManager_test.cpp
index 84c5774..e1c6d7d 100644
--- a/statsd/tests/ConfigManager_test.cpp
+++ b/statsd/tests/ConfigManager_test.cpp
@@ -161,3 +161,96 @@
     manager->RemoveConfigs(1);
     manager->RemoveConfigs(3);
 }
+
+TEST(ConfigManagerTest, TestSendRestrictedMetricsChangedBroadcast) {
+    const string configPackage = "pkg";
+    const int64_t configId = 123;
+    const int32_t callingUid = 456;
+    const vector<int64_t> metricIds = {100, 200, 999};
+
+    shared_ptr<MockPendingIntentRef> pir = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir, sendRestrictedMetricsChangedBroadcast(metricIds))
+            .Times(1)
+            .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+    sp<ConfigManager> manager = new ConfigManager();
+    manager->SetRestrictedMetricsChangedReceiver(configPackage, configId, callingUid, pir);
+
+    manager->SendRestrictedMetricsBroadcast({configPackage}, configId, {callingUid}, metricIds);
+}
+
+TEST(ConfigManagerTest, TestSendRestrictedMetricsChangedBroadcastMultipleConfigs) {
+    const string package1 = "pkg1", package2 = "pkg2", package3 = "pkg3";
+    const int64_t configId = 123;
+    const int32_t callingUid1 = 456, callingUid2 = 789, callingUid3 = 1111;
+    const vector<int64_t> metricIds1 = {100, 200, 999}, metricIds2 = {101}, metricIds3 = {202, 101};
+
+    shared_ptr<MockPendingIntentRef> pir1 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir1, sendRestrictedMetricsChangedBroadcast(metricIds1))
+            .Times(3)
+            .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+    EXPECT_CALL(*pir1, sendRestrictedMetricsChangedBroadcast(metricIds2))
+            .Times(1)
+            .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+    shared_ptr<MockPendingIntentRef> pir2 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir2, sendRestrictedMetricsChangedBroadcast(metricIds1))
+            .Times(1)
+            .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+    shared_ptr<MockPendingIntentRef> pir3 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir3, sendRestrictedMetricsChangedBroadcast(metricIds1))
+            .Times(1)
+            .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+    EXPECT_CALL(*pir3, sendRestrictedMetricsChangedBroadcast(metricIds2))
+            .Times(1)
+            .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+    shared_ptr<MockPendingIntentRef> pir4 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir4, sendRestrictedMetricsChangedBroadcast(metricIds1))
+            .Times(2)
+            .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+    shared_ptr<MockPendingIntentRef> pir5 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir5, sendRestrictedMetricsChangedBroadcast(metricIds3))
+            .Times(1)
+            .WillRepeatedly(Invoke([](const vector<int64_t>&) { return Status::ok(); }));
+
+    sp<ConfigManager> manager = new ConfigManager();
+    manager->SetRestrictedMetricsChangedReceiver(package1, configId, callingUid1, pir1);
+    manager->SetRestrictedMetricsChangedReceiver(package2, configId, callingUid2, pir2);
+    manager->SetRestrictedMetricsChangedReceiver(package1, configId, callingUid2, pir3);
+    manager->SetRestrictedMetricsChangedReceiver(package2, configId, callingUid1, pir4);
+    manager->SetRestrictedMetricsChangedReceiver(package3, configId, callingUid3, pir5);
+
+    // Invoke pir 1, 2, 3, 4.
+    manager->SendRestrictedMetricsBroadcast({package1, package2}, configId,
+                                            {callingUid1, callingUid2}, metricIds1);
+    // Invoke pir 1 only
+    manager->SendRestrictedMetricsBroadcast({package1}, configId, {callingUid1}, metricIds1);
+    // Invoke pir 1, 4.
+    manager->SendRestrictedMetricsBroadcast({package1, package2}, configId, {callingUid1},
+                                            metricIds1);
+    // Invoke pir 1, 3
+    manager->SendRestrictedMetricsBroadcast({package1}, configId, {callingUid1, callingUid2},
+                                            metricIds2);
+    // Invoke pir 5
+    manager->SendRestrictedMetricsBroadcast({package3}, configId, {callingUid1, callingUid3},
+                                            metricIds3);
+}
+
+TEST(ConfigManagerTest, TestRemoveRestrictedMetricsChangedBroadcast) {
+    const string configPackage = "pkg";
+    const int64_t configId = 123;
+    const int32_t callingUid = 456;
+    const vector<int64_t> metricIds = {100, 200, 999};
+
+    shared_ptr<MockPendingIntentRef> pir = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir, sendRestrictedMetricsChangedBroadcast(metricIds)).Times(0);
+
+    sp<ConfigManager> manager = new ConfigManager();
+    manager->SetRestrictedMetricsChangedReceiver(configPackage, configId, callingUid, pir);
+    manager->RemoveRestrictedMetricsChangedReceiver(configPackage, configId, callingUid);
+
+    manager->SendRestrictedMetricsBroadcast({configPackage}, configId, {callingUid}, metricIds);
+}
\ No newline at end of file
diff --git a/statsd/tests/FieldValue_test.cpp b/statsd/tests/FieldValue_test.cpp
index 64be559..8e9f789 100644
--- a/statsd/tests/FieldValue_test.cpp
+++ b/statsd/tests/FieldValue_test.cpp
@@ -158,6 +158,40 @@
     EXPECT_EQ("some value", output.getValues()[6].mValue.str_value);
 }
 
+TEST(AtomMatcherTest, TestFilter_FIRST) {
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::FIRST);
+
+    child->add_child()->set_field(1);
+    child->add_child()->set_field(2);
+
+    child = matcher1.add_child();
+    child->set_field(2);
+
+    vector<Matcher> matchers;
+    translateFieldMatcher(matcher1, &matchers);
+
+    std::vector<int> attributionUids = {1111, 2222, 3333};
+    std::vector<string> attributionTags = {"location1", "location2", "location3"};
+
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event, 10 /*atomId*/, 1012345, attributionUids, attributionTags, "some value");
+    HashableDimensionKey output;
+
+    filterValues(matchers, event.getValues(), &output);
+
+    ASSERT_EQ((size_t)3, output.getValues().size());
+    EXPECT_EQ((int32_t)0x02010101, output.getValues()[0].mField.getField());
+    EXPECT_EQ((int32_t)1111, output.getValues()[0].mValue.int_value);
+    EXPECT_EQ((int32_t)0x02010102, output.getValues()[1].mField.getField());
+    EXPECT_EQ("location1", output.getValues()[1].mValue.str_value);
+    EXPECT_EQ((int32_t)0x00020000, output.getValues()[2].mField.getField());
+    EXPECT_EQ("some value", output.getValues()[2].mValue.str_value);
+};
+
 TEST(AtomMatcherTest, TestFilterRepeated_FIRST) {
     FieldMatcher matcher;
     matcher.set_field(123);
@@ -508,6 +542,124 @@
     }
 }
 
+TEST(AtomMatcherTest, TestDedupFieldMatchersAllDifferent) {
+    // Matchers: Fields 1, 2, 3
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(3);
+
+    vector<Matcher> fieldMatchers;
+    translateFieldMatcher(matcher1, &fieldMatchers);
+    ASSERT_EQ(3, fieldMatchers.size());
+
+    // Deduped Matchers: Fields 1, 2, 3
+    std::vector<Matcher> dedupedFieldMatchers = dedupFieldMatchers(fieldMatchers);
+    ASSERT_EQ((size_t)3, dedupedFieldMatchers.size());
+    EXPECT_EQ(fieldMatchers[0], dedupedFieldMatchers[0]);
+    EXPECT_EQ(fieldMatchers[1], dedupedFieldMatchers[1]);
+    EXPECT_EQ(fieldMatchers[2], dedupedFieldMatchers[2]);
+}
+
+TEST(AtomMatcherTest, TestDedupFieldMatchersAllSame) {
+    // Matcher: Fields 1, 1, 1
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(1);
+
+    vector<Matcher> fieldMatchers;
+    translateFieldMatcher(matcher1, &fieldMatchers);
+    ASSERT_EQ(3, fieldMatchers.size());
+
+    // Deduped Matchers: Fields 1, 1, 1
+    std::vector<Matcher> dedupedFieldMatchers = dedupFieldMatchers(fieldMatchers);
+    ASSERT_EQ((size_t)1, dedupedFieldMatchers.size());
+    EXPECT_EQ(fieldMatchers[0], dedupedFieldMatchers[0]);
+}
+
+TEST(AtomMatcherTest, TestDedupFieldMatcherMixOfFields) {
+    // Matcher: Fields 2, 2, 1, 3, 2, 1, 3
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(3);
+    child = matcher1.add_child();
+    child->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(3);
+
+    vector<Matcher> fieldMatchers;
+    translateFieldMatcher(matcher1, &fieldMatchers);
+    ASSERT_EQ(7, fieldMatchers.size());
+
+    // Deduped Matchers: Fields 2, 1, 3
+    std::vector<Matcher> dedupedFieldMatchers = dedupFieldMatchers(fieldMatchers);
+    ASSERT_EQ((size_t)3, dedupedFieldMatchers.size());
+    EXPECT_EQ(fieldMatchers[0], dedupedFieldMatchers[0]);
+    EXPECT_EQ(fieldMatchers[2], dedupedFieldMatchers[1]);
+    EXPECT_EQ(fieldMatchers[3], dedupedFieldMatchers[2]);
+}
+
+TEST(AtomMatcherTest, TestDedupFieldMatcherDifferentPositionSameFields) {
+    // Matcher: Fields 3, 1.1(FIRST), 1.2(FIRST), 1.1(FIRST), 1.1(LAST), 1.2(FIRST), 2
+    FieldMatcher matcher1;
+    matcher1.set_field(10);
+    FieldMatcher* child = matcher1.add_child();
+    child->set_field(3);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::FIRST);
+    child->add_child()->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::FIRST);
+    child->add_child()->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::FIRST);
+    child->add_child()->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::LAST);
+    child->add_child()->set_field(1);
+    child = matcher1.add_child();
+    child->set_field(1);
+    child->set_position(Position::FIRST);
+    child->add_child()->set_field(2);
+    child = matcher1.add_child();
+    child->set_field(2);
+
+    vector<Matcher> fieldMatchers;
+    translateFieldMatcher(matcher1, &fieldMatchers);
+    ASSERT_EQ(7, fieldMatchers.size());
+
+    // Deduped Matchers: Fields 3, 1.1(FIRST), 1.2(FIRST), 1.1(LAST) 2
+    std::vector<Matcher> dedupedFieldMatchers = dedupFieldMatchers(fieldMatchers);
+    ASSERT_EQ((size_t)5, dedupedFieldMatchers.size());
+    EXPECT_EQ(fieldMatchers[0], dedupedFieldMatchers[0]);
+    EXPECT_EQ(fieldMatchers[1], dedupedFieldMatchers[1]);
+    EXPECT_EQ(fieldMatchers[2], dedupedFieldMatchers[2]);
+    EXPECT_EQ(fieldMatchers[4], dedupedFieldMatchers[3]);
+    EXPECT_EQ(fieldMatchers[6], dedupedFieldMatchers[4]);
+}
+
 void checkAttributionNodeInDimensionsValueParcel(StatsDimensionsValueParcel& attributionNodeParcel,
                                                  int32_t nodeDepthInAttributionChain,
                                                  int32_t uid, string tag) {
@@ -739,7 +891,7 @@
     bool boolArray[boolArrayLength];
     boolArray[0] = 1;
     boolArray[1] = 0;
-    vector<bool> boolArrayVector = {1, 0};
+    vector<uint8_t> boolArrayVector = {1, 0};
     vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF};
 
     unique_ptr<LogEvent> event = CreateTestAtomReportedEventVariableRepeatedFields(
@@ -904,6 +1056,138 @@
     EXPECT_FALSE(subsetDimensions(matchers2, matchers1));
 }
 
+/*
+ * Test multiple combinations of repeated field matchers with different positions.
+ */
+TEST(AtomMatcherTest, TestSubsetDimensions_RepeatedFields) {
+    // Initialize matchers with position ALL.
+    FieldMatcher matcherAll;
+    matcherAll.set_field(10);
+    FieldMatcher* child = matcherAll.add_child();
+    child->set_field(1);
+    child = matcherAll.add_child();
+    child->set_field(2);
+    child->set_position(Position::ALL);
+    FieldMatcher* attributionNodeChild = child->add_child();
+    attributionNodeChild->set_field(1);
+
+    vector<Matcher> matchersAll;
+    translateFieldMatcher(matcherAll, &matchersAll);
+
+    // Initialize matchers with position FIRST.
+    FieldMatcher matcherFirst;
+    matcherFirst.set_field(10);
+    child = matcherFirst.add_child();
+    child->set_field(1);
+    child = matcherFirst.add_child();
+    child->set_field(2);
+    child->set_position(Position::FIRST);
+    attributionNodeChild = child->add_child();
+    attributionNodeChild->set_field(1);
+
+    vector<Matcher> matchersFirst;
+    translateFieldMatcher(matcherFirst, &matchersFirst);
+
+    // Initialize matchers with position LAST.
+    FieldMatcher matcherLast;
+    matcherLast.set_field(10);
+    child = matcherLast.add_child();
+    child->set_field(1);
+    child = matcherLast.add_child();
+    child->set_field(2);
+    child->set_position(Position::LAST);
+    attributionNodeChild = child->add_child();
+    attributionNodeChild->set_field(1);
+
+    vector<Matcher> matchersLast;
+    translateFieldMatcher(matcherLast, &matchersLast);
+
+    // Initialize matchers with position ANY.
+    FieldMatcher matcherAny;
+    matcherAny.set_field(10);
+    child = matcherAny.add_child();
+    child->set_field(1);
+    child = matcherAny.add_child();
+    child->set_field(2);
+    child->set_position(Position::ANY);
+    attributionNodeChild = child->add_child();
+    attributionNodeChild->set_field(1);
+
+    vector<Matcher> matchersAny;
+    translateFieldMatcher(matcherAny, &matchersAny);
+
+    // Initialize matchers with position ALL, different field number.
+    FieldMatcher matcherAllDifferent;
+    matcherAllDifferent.set_field(10);
+    child = matcherAllDifferent.add_child();
+    child->set_field(1);
+    child = matcherAllDifferent.add_child();
+    child->set_field(2);
+    child->set_position(Position::ALL);
+    attributionNodeChild = child->add_child();
+    attributionNodeChild->set_field(2);
+
+    vector<Matcher> matchersAllDifferent;
+    translateFieldMatcher(matcherAllDifferent, &matchersAllDifferent);
+
+    // Positions ALL, FIRST, LAST are subsets of position ALL.
+    EXPECT_TRUE(subsetDimensions(matchersAll, matchersAll));
+    EXPECT_TRUE(subsetDimensions(matchersFirst, matchersAll));
+    EXPECT_TRUE(subsetDimensions(matchersLast, matchersAll));
+    EXPECT_FALSE(subsetDimensions(matchersAny, matchersAll));
+    EXPECT_FALSE(subsetDimensions(matchersAllDifferent, matchersAll));
+
+    // Position FIRST is a subset of position FIRST.
+    EXPECT_FALSE(subsetDimensions(matchersAll, matchersFirst));
+    EXPECT_TRUE(subsetDimensions(matchersFirst, matchersFirst));
+    EXPECT_FALSE(subsetDimensions(matchersLast, matchersFirst));
+    EXPECT_FALSE(subsetDimensions(matchersAny, matchersFirst));
+    EXPECT_FALSE(subsetDimensions(matchersAllDifferent, matchersFirst));
+
+    // Position LAST is a subset of position LAST.
+    EXPECT_FALSE(subsetDimensions(matchersAll, matchersLast));
+    EXPECT_FALSE(subsetDimensions(matchersFirst, matchersLast));
+    EXPECT_TRUE(subsetDimensions(matchersLast, matchersLast));
+    EXPECT_FALSE(subsetDimensions(matchersAny, matchersLast));
+    EXPECT_FALSE(subsetDimensions(matchersAllDifferent, matchersLast));
+
+    // Position ANY is a subset of position ANY.
+    EXPECT_FALSE(subsetDimensions(matchersAll, matchersAny));
+    EXPECT_FALSE(subsetDimensions(matchersFirst, matchersAny));
+    EXPECT_FALSE(subsetDimensions(matchersLast, matchersAny));
+    EXPECT_TRUE(subsetDimensions(matchersAny, matchersAny));
+    EXPECT_FALSE(subsetDimensions(matchersAllDifferent, matchersAny));
+}
+
+TEST(AtomMatcherTest, TestAllPositionMatcher) {
+    // Initialize matcher with position ALL.
+    FieldMatcher matcherAll;
+    matcherAll.set_field(10);
+    FieldMatcher* child = matcherAll.add_child();
+    child->set_field(2);
+    child->set_position(Position::ALL);
+    FieldMatcher* attributionNodeChild = child->add_child();
+    attributionNodeChild->set_field(1);
+
+    vector<Matcher> matchersAll;
+    translateFieldMatcher(matcherAll, &matchersAll);
+
+    // Initialize matcher with position ANY.
+    FieldMatcher matcherAny;
+    matcherAny.set_field(10);
+    child = matcherAny.add_child();
+    child->set_field(2);
+    child->set_position(Position::ANY);
+    attributionNodeChild = child->add_child();
+    attributionNodeChild->set_field(1);
+
+    vector<Matcher> matchersAny;
+    translateFieldMatcher(matcherAny, &matchersAny);
+
+    EXPECT_TRUE(matchersAll[0].hasAllPositionMatcher());
+    EXPECT_FALSE(matchersAny[0].hasAllPositionMatcher());
+}
+
 TEST(AtomMatcherTest, TestIsPrimitiveRepeatedField) {
     int pos1[] = {1, 1, 1};  // attribution uid
     int pos2[] = {1, 1, 2};  // attribution tag
diff --git a/statsd/tests/LogEntryMatcher_test.cpp b/statsd/tests/LogEntryMatcher_test.cpp
index f625fc4..75ec3b5 100644
--- a/statsd/tests/LogEntryMatcher_test.cpp
+++ b/statsd/tests/LogEntryMatcher_test.cpp
@@ -15,15 +15,16 @@
 #include <gtest/gtest.h>
 #include <stdio.h>
 
-#include "annotations.h"
-#include "src/statsd_config.pb.h"
 #include "matchers/matcher_util.h"
+#include "src/statsd_config.pb.h"
+#include "stats_annotations.h"
 #include "stats_event.h"
 #include "stats_log_util.h"
 #include "stats_util.h"
 #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;
@@ -120,7 +121,7 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_writeInt32Array(statsEvent, intArray.data(), intArray.size());
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
@@ -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));
+            "PkG3");
+    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));
+            "Pkg2");
+    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));
+            "PkG3");
+    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));
+            "Pkg2");
+    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));
+            "PkG3");
+    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));
+            "Pkg2");
+    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");
+            "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");
+            "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");
+            "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");
+            "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");
+            "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");
+            "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");
+            "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");
+            "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");
+            "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,20 +408,20 @@
     // 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);
-    makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ANNOTATION_ID_IS_UID, true);
+    makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ASTATSLOG_ANNOTATION_ID_IS_UID,
+                                      true);
 
     // 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));
+    simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("Pkg2");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 }
 
 TEST(AtomMatcherTest, TestRepeatedUidFieldMatcher) {
@@ -449,35 +450,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));
+    fieldValueMatcher->set_eq_string("Pkg2");
+    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));
-    fieldValueMatcher->set_eq_string("pkg2");  // package names are normalized
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
+    fieldValueMatcher->set_eq_string("Pkg2");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2).matched);
 }
 
 TEST(AtomMatcherTest, TestNeqAnyStringMatcher_SingleString) {
@@ -497,17 +498,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) {
@@ -543,33 +544,33 @@
     auto neqStringList = attributionMatcher->mutable_matches_tuple()
                                  ->mutable_field_value_matcher(0)
                                  ->mutable_neq_any_string();
-    neqStringList->add_str_value("pkg2");
-    neqStringList->add_str_value("pkg3");
+    neqStringList->add_str_value("Pkg2");
+    neqStringList->add_str_value("PkG3");
 
     auto fieldMatcher = simpleMatcher->add_field_value_matcher();
     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));
+    neqStringList->add_str_value("PkG3");
+    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) {
@@ -612,29 +613,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) {
@@ -655,19 +656,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) {
@@ -685,7 +686,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) {
@@ -705,16 +706,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) {
@@ -735,28 +736,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) {
@@ -777,34 +778,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) {
@@ -825,31 +826,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) {
@@ -870,43 +871,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) {
@@ -927,43 +928,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) {
@@ -984,15 +985,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) {
@@ -1013,43 +1014,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) {
@@ -1065,20 +1066,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.
@@ -1225,17 +1226,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, ANNOTATION_ID_IS_UID, true);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+    makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID, 1111, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+    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, ANNOTATION_ID_IS_UID, true);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event3));
+    makeIntWithBoolAnnotationLogEvent(&event3, TAG_ID, 3333, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event3).matched);
 
     // Update matcher to match one AID
     simpleMatcher->mutable_field_value_matcher(0)->set_eq_wildcard_string(
@@ -1243,30 +1244,30 @@
 
     // Event where mapping from uid to aid doesn't fit wildcard pattern.
     LogEvent event4(/*uid=*/0, /*pid=*/0);
-    makeIntWithBoolAnnotationLogEvent(&event4, TAG_ID, 1005, ANNOTATION_ID_IS_UID, true);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4));
+    makeIntWithBoolAnnotationLogEvent(&event4, TAG_ID, 1005, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+    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, ANNOTATION_ID_IS_UID, true);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event5));
+    makeIntWithBoolAnnotationLogEvent(&event5, TAG_ID, 1000, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+    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_*");
 
     // Event where mapping from uid to aid doesn't fit wildcard pattern.
     LogEvent event6(/*uid=*/0, /*pid=*/0);
-    makeIntWithBoolAnnotationLogEvent(&event6, TAG_ID, 1036, ANNOTATION_ID_IS_UID, true);
-    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event6));
+    makeIntWithBoolAnnotationLogEvent(&event6, TAG_ID, 1036, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+    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, ANNOTATION_ID_IS_UID, true);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event7));
+    makeIntWithBoolAnnotationLogEvent(&event7, TAG_ID, 1034, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event7).matched);
 
     LogEvent event8(/*uid=*/0, /*pid=*/0);
-    makeIntWithBoolAnnotationLogEvent(&event8, TAG_ID, 1035, ANNOTATION_ID_IS_UID, true);
-    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event8));
+    makeIntWithBoolAnnotationLogEvent(&event8, TAG_ID, 1035, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event8).matched);
 }
 
 TEST(AtomMatcherTest, TestWildcardStringMatcher) {
@@ -1283,55 +1284,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) {
@@ -1351,17 +1354,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) {
@@ -1388,27 +1391,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"};
@@ -1416,7 +1419,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) {
@@ -1436,17 +1439,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) {
@@ -1472,31 +1475,593 @@
 
     // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"(*[0-9]+$)");  // 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, TestStringReplaceRegexWithSubgroup) {
+    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"(([a-z]+)[0-9]+$)");  // "([a-z]+)" is a subgroup.
+    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/LogEvent_test.cpp b/statsd/tests/LogEvent_test.cpp
index c9a90dd..f53e247 100644
--- a/statsd/tests/LogEvent_test.cpp
+++ b/statsd/tests/LogEvent_test.cpp
@@ -16,9 +16,11 @@
 
 #include <gtest/gtest.h>
 
+#include "flags/FlagProvider.h"
 #include "frameworks/proto_logging/stats/atoms.pb.h"
 #include "frameworks/proto_logging/stats/enums/stats/launcher/launcher.pb.h"
 #include "log/log_event_list.h"
+#include "stats_annotations.h"
 #include "stats_event.h"
 #include "statsd_test_util.h"
 
@@ -35,7 +37,8 @@
 
 namespace {
 
-Field getField(int32_t tag, const vector<int32_t>& pos, int32_t depth, const vector<bool>& last) {
+Field getField(int32_t tag, const vector<int32_t>& pos, int32_t depth,
+               const vector<uint8_t>& last) {
     Field f(tag, (int32_t*)pos.data(), depth);
 
     // only decorate last position for depths with repeated fields (depth 1)
@@ -86,6 +89,50 @@
     return logEvent->isValid();
 }
 
+bool createAtomLevelIntAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
+                                          int annotationValue, bool doHeaderPrefetch) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+    AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue);
+    fillStatsEventWithSampleValue(statsEvent, typeId);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    if (doHeaderPrefetch) {
+        // Testing LogEvent header prefetch logic
+        const LogEvent::BodyBufferInfo bodyInfo = logEvent->parseHeader(buf, size);
+        logEvent->parseBody(bodyInfo);
+    } else {
+        logEvent->parseBuffer(buf, size);
+    }
+    AStatsEvent_release(statsEvent);
+
+    return logEvent->isValid();
+}
+
+bool createAtomLevelBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
+                                           bool annotationValue, bool doHeaderPrefetch) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+    AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
+    fillStatsEventWithSampleValue(statsEvent, typeId);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    if (doHeaderPrefetch) {
+        // Testing LogEvent header prefetch logic
+        const LogEvent::BodyBufferInfo bodyInfo = logEvent->parseHeader(buf, size);
+        logEvent->parseBody(bodyInfo);
+    } else {
+        logEvent->parseBuffer(buf, size);
+    }
+    AStatsEvent_release(statsEvent);
+
+    return logEvent->isValid();
+}
+
 }  // anonymous namespace
 
 // Setup for parameterized tests.
@@ -657,8 +704,8 @@
 
 TEST_P(LogEventTest, TestAnnotationIdIsUid) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_IS_UID,
-                                                      true,
+    EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
+                                                      ASTATSLOG_ANNOTATION_ID_IS_UID, true,
                                                       /*doHeaderPrefetch=*/GetParam()));
 
     ASSERT_EQ(event.getNumUidFields(), 1);
@@ -682,7 +729,7 @@
     AStatsEvent_setAtomId(statsEvent, 100);
     AStatsEvent_writeInt32(statsEvent, 5);
     AStatsEvent_writeInt32Array(statsEvent, int32Array, numElements);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     AStatsEvent_writeStringArray(statsEvent, cStringArray, numElements);
     AStatsEvent_build(statsEvent);
 
@@ -708,7 +755,7 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, 100);
     AStatsEvent_writeInt32Array(statsEvent, int32Array, numElements);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     AStatsEvent_build(statsEvent);
 
     size_t size;
@@ -728,7 +775,7 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, 100);
     AStatsEvent_writeInt32Array(statsEvent, int32Array, /*numElements*/ 0);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     AStatsEvent_writeInt32(statsEvent, 5);
     AStatsEvent_build(statsEvent);
 
@@ -748,7 +795,7 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
     AStatsEvent_writeInt64Array(statsEvent, int64Array, /*numElements*/ 2);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     AStatsEvent_build(statsEvent);
 
     size_t size;
@@ -772,7 +819,7 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
     AStatsEvent_writeStringArray(statsEvent, cStringArray, /*numElements*/ 2);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     AStatsEvent_build(statsEvent);
 
     size_t size;
@@ -790,21 +837,22 @@
 
     if (std::get<0>(GetParam()) != INT32_TYPE && std::get<0>(GetParam()) != LIST_TYPE) {
         EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
-                &event, std::get<0>(GetParam()), ANNOTATION_ID_IS_UID, true,
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_IS_UID, true,
                 /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
 
 TEST_P(LogEventTest, TestAnnotationIdIsUid_NotIntAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_IS_UID, 10,
+    EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
+                                                      ASTATSLOG_ANNOTATION_ID_IS_UID, 10,
                                                       /*doHeaderPrefetch=*/GetParam()));
 }
 
 TEST_P(LogEventTest, TestAnnotationIdStateNested) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
     EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
-                                                      ANNOTATION_ID_STATE_NESTED, true,
+                                                      ASTATSLOG_ANNOTATION_ID_STATE_NESTED, true,
                                                       /*doHeaderPrefetch=*/GetParam()));
 
     const vector<FieldValue>& values = event.getValues();
@@ -817,7 +865,7 @@
 
     if (std::get<0>(GetParam()) != INT32_TYPE) {
         EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
-                &event, std::get<0>(GetParam()), ANNOTATION_ID_STATE_NESTED, true,
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_STATE_NESTED, true,
                 /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
@@ -825,14 +873,14 @@
 TEST_P(LogEventTest, TestAnnotationIdStateNested_NotIntAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
     EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
-                                                      ANNOTATION_ID_STATE_NESTED, 10,
+                                                      ASTATSLOG_ANNOTATION_ID_STATE_NESTED, 10,
                                                       /*doHeaderPrefetch=*/GetParam()));
 }
 
 TEST_P(LogEventTest, TestPrimaryFieldAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
     EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
-                                                      ANNOTATION_ID_PRIMARY_FIELD, true,
+                                                      ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true,
                                                       /*doHeaderPrefetch=*/GetParam()));
 
     const vector<FieldValue>& values = event.getValues();
@@ -845,7 +893,7 @@
 
     if (std::get<0>(GetParam()) == LIST_TYPE || std::get<0>(GetParam()) == ATTRIBUTION_CHAIN_TYPE) {
         EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
-                &event, std::get<0>(GetParam()), ANNOTATION_ID_PRIMARY_FIELD, true,
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true,
                 /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
@@ -853,14 +901,14 @@
 TEST_P(LogEventTest, TestPrimaryFieldAnnotation_NotIntAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
     EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
-                                                      ANNOTATION_ID_PRIMARY_FIELD, 10,
+                                                      ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, 10,
                                                       /*doHeaderPrefetch=*/GetParam()));
 }
 
 TEST_P(LogEventTest, TestExclusiveStateAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
     EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
-                                                      ANNOTATION_ID_EXCLUSIVE_STATE, true,
+                                                      ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true,
                                                       /*doHeaderPrefetch=*/GetParam()));
 
     const vector<FieldValue>& values = event.getValues();
@@ -873,7 +921,7 @@
 
     if (std::get<0>(GetParam()) != INT32_TYPE) {
         EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
-                &event, std::get<0>(GetParam()), ANNOTATION_ID_EXCLUSIVE_STATE, true,
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true,
                 /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
@@ -881,7 +929,7 @@
 TEST_P(LogEventTest, TestExclusiveStateAnnotation_NotIntAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
     EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
-                                                      ANNOTATION_ID_EXCLUSIVE_STATE, 10,
+                                                      ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, 10,
                                                       /*doHeaderPrefetch=*/GetParam()));
 }
 
@@ -901,7 +949,8 @@
         AStatsEvent_writeInt32(statsEvent, 10);
     }
     AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID,
+                                  true);
     AStatsEvent_build(statsEvent);
 
     // Construct LogEvent
@@ -922,46 +971,83 @@
 
     if (std::get<0>(GetParam()) != ATTRIBUTION_CHAIN_TYPE) {
         EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
-                &event, std::get<0>(GetParam()), ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true,
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID,
+                true,
                 /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
 
 TEST_P(LogEventTest, TestPrimaryFieldFirstUidAnnotation_NotIntAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, ATTRIBUTION_CHAIN_TYPE,
-                                                      ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, 10,
-                                                      /*doHeaderPrefetch=*/GetParam()));
+    EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(
+            &event, ATTRIBUTION_CHAIN_TYPE, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, 10,
+            /*doHeaderPrefetch=*/GetParam()));
 }
 
 TEST_P(LogEventTest, TestResetStateAnnotation) {
     int32_t resetState = 10;
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    EXPECT_TRUE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
-                                                     ANNOTATION_ID_TRIGGER_STATE_RESET, resetState,
-                                                     /*doHeaderPrefetch=*/GetParam()));
+    EXPECT_TRUE(createFieldWithIntAnnotationLogEvent(
+            &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET, resetState,
+            /*doHeaderPrefetch=*/GetParam()));
 
     const vector<FieldValue>& values = event.getValues();
     ASSERT_EQ(values.size(), 1);
     EXPECT_EQ(event.getResetState(), resetState);
 }
 
+TEST_P(LogEventTest, TestRestrictionCategoryAnnotation) {
+    if (!isAtLeastU()) {
+        GTEST_SKIP();
+    }
+    int32_t restrictionCategory = ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC;
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    EXPECT_TRUE(createAtomLevelIntAnnotationLogEvent(
+            &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY, restrictionCategory,
+            /*doHeaderPrefetch=*/GetParam()));
+
+    ASSERT_EQ(event.getRestrictionCategory(), restrictionCategory);
+}
+
+TEST_P(LogEventTest, TestInvalidRestrictionCategoryAnnotation) {
+    if (!isAtLeastU()) {
+        GTEST_SKIP();
+    }
+    int32_t restrictionCategory = 619;  // unknown category
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    EXPECT_FALSE(createAtomLevelIntAnnotationLogEvent(
+            &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY, restrictionCategory,
+            /*doHeaderPrefetch=*/GetParam()));
+}
+
+TEST_P(LogEventTest, TestRestrictionCategoryAnnotationBelowUDevice) {
+    if (isAtLeastU()) {
+        GTEST_SKIP();
+    }
+    int32_t restrictionCategory = ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC;
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    EXPECT_FALSE(createAtomLevelIntAnnotationLogEvent(
+            &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY, restrictionCategory,
+            /*doHeaderPrefetch=*/GetParam()));
+}
+
 TEST_P(LogEventTestBadAnnotationFieldTypes, TestResetStateAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
     int32_t resetState = 10;
 
     if (std::get<0>(GetParam()) != INT32_TYPE) {
         EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(
-                &event, std::get<0>(GetParam()), ANNOTATION_ID_TRIGGER_STATE_RESET, resetState,
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET,
+                resetState,
                 /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
 
 TEST_P(LogEventTest, TestResetStateAnnotation_NotBoolAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
-                                                       ANNOTATION_ID_TRIGGER_STATE_RESET, true,
-                                                       /*doHeaderPrefetch=*/GetParam()));
+    EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+            &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET, true,
+            /*doHeaderPrefetch=*/GetParam()));
 }
 
 TEST_P(LogEventTest, TestUidAnnotationWithInt8MaxValues) {
@@ -977,7 +1063,7 @@
     AStatsEvent_writeInt32Array(event, int32Array, numElements);
     AStatsEvent_writeInt32(event, 10);
     AStatsEvent_writeInt32(event, 11);
-    AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(event, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     AStatsEvent_build(event);
 
     size_t size;
@@ -997,7 +1083,7 @@
 
     AStatsEvent_writeInt32(event, 10);
     AStatsEvent_writeAttributionChain(event, uids, tags, 0);
-    AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+    AStatsEvent_addBoolAnnotation(event, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
 
     AStatsEvent_build(event);
 
@@ -1010,6 +1096,103 @@
     AStatsEvent_release(event);
 }
 
+// Setup for parameterized tests.
+class LogEvent_FieldRestrictionTest : public testing::TestWithParam<std::tuple<int, bool>> {
+public:
+    static std::string ToString(testing::TestParamInfo<std::tuple<int, bool>> info) {
+        const std::string boolName = std::get<1>(info.param) ? "_prefetchTrue" : "_prefetchFalse";
+
+        switch (std::get<0>(info.param)) {
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO:
+                return "PeripheralDeviceInfo" + boolName;
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE:
+                return "AppUsage" + boolName;
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY:
+                return "AppActivity" + boolName;
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT:
+                return "HealthConnect" + boolName;
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY:
+                return "Accessibility" + boolName;
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH:
+                return "SystemSearch" + boolName;
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT:
+                return "UserEngagement" + boolName;
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING:
+                return "AmbientSensing" + boolName;
+            case ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION:
+                return "DemographicClassification" + boolName;
+            default:
+                return "Unknown" + boolName;
+        }
+    }
+    void TearDown() override {
+        FlagProvider::getInstance().resetOverrides();
+    }
+};
+
+// TODO(b/222539899): Add BOOL_TYPE value once parseAnnotations is updated to check specific
+// typeIds. BOOL_TYPE should be a bad field type for is_uid, nested, and reset state annotations.
+INSTANTIATE_TEST_SUITE_P(
+        LogEvent_FieldRestrictionTest, LogEvent_FieldRestrictionTest,
+        testing::Combine(
+                testing::Values(
+                        ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_PERIPHERAL_DEVICE_INFO,
+                        ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_USAGE,
+                        ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_APP_ACTIVITY,
+                        ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_HEALTH_CONNECT,
+                        ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_ACCESSIBILITY,
+                        ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_SYSTEM_SEARCH,
+                        ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_USER_ENGAGEMENT,
+                        ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_AMBIENT_SENSING,
+                        ASTATSLOG_ANNOTATION_ID_FIELD_RESTRICTION_DEMOGRAPHIC_CLASSIFICATION),
+                testing::Bool()),
+        LogEvent_FieldRestrictionTest::ToString);
+
+TEST_P(LogEvent_FieldRestrictionTest, TestFieldRestrictionAnnotation) {
+    if (!isAtLeastU()) {
+        GTEST_SKIP();
+    }
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    EXPECT_TRUE(
+            createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, std::get<0>(GetParam()), true,
+                                                  /*doHeaderPrefetch=*/std::get<1>(GetParam())));
+    // Some basic checks to make sure the event is parsed correctly.
+    EXPECT_EQ(event.GetTagId(), 100);
+    ASSERT_EQ(event.getValues().size(), 1);
+    EXPECT_EQ(event.getValues()[0].mValue.getType(), Type::INT);
+}
+
+TEST_P(LogEvent_FieldRestrictionTest, TestInvalidAnnotationIntType) {
+    if (!isAtLeastU()) {
+        GTEST_SKIP();
+    }
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(
+            &event, STRING_TYPE, std::get<0>(GetParam()),
+            /*random int*/ 15, /*doHeaderPrefetch=*/std::get<1>(GetParam())));
+}
+
+TEST_P(LogEvent_FieldRestrictionTest, TestInvalidAnnotationAtomLevel) {
+    if (!isAtLeastU()) {
+        GTEST_SKIP();
+    }
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    EXPECT_FALSE(createAtomLevelBoolAnnotationLogEvent(
+            &event, STRING_TYPE, std::get<0>(GetParam()), true,
+            /*doHeaderPrefetch=*/std::get<1>(GetParam())));
+}
+
+TEST_P(LogEvent_FieldRestrictionTest, TestRestrictionCategoryAnnotationBelowUDevice) {
+    if (isAtLeastU()) {
+        GTEST_SKIP();
+    }
+    int32_t restrictionCategory = ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC;
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    EXPECT_FALSE(
+            createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, std::get<0>(GetParam()), true,
+                                                  /*doHeaderPrefetch=*/std::get<1>(GetParam())));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/MetricsManager_test.cpp b/statsd/tests/MetricsManager_test.cpp
index e8a7e07..d86e975 100644
--- a/statsd/tests/MetricsManager_test.cpp
+++ b/statsd/tests/MetricsManager_test.cpp
@@ -48,48 +48,24 @@
 namespace statsd {
 
 namespace {
-const ConfigKey kConfigKey(0, 12345);
+const int kConfigId = 12345;
+const ConfigKey kConfigKey(0, kConfigId);
 
 const long timeBaseSec = 1000;
 
-StatsdConfig buildGoodConfig() {
+StatsdConfig buildGoodRestrictedConfig() {
     StatsdConfig config;
-    config.set_id(12345);
+    config.set_id(kConfigId);
+    config.set_restricted_metrics_delegate_package_name("delegate");
 
     AtomMatcher* eventMatcher = config.add_atom_matcher();
     eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
-
     SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
     simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
 
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
-
-    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(2 /*SCREEN_STATE_CHANGE*/);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
-
-    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher(StringToId("SCREEN_IS_ON"));
-    combination->add_matcher(StringToId("SCREEN_IS_OFF"));
-
-    CountMetric* metric = config.add_count_metric();
+    EventMetric* metric = config.add_event_metric();
     metric->set_id(3);
     metric->set_what(StringToId("SCREEN_IS_ON"));
-    metric->set_bucket(ONE_MINUTE);
-    metric->mutable_dimensions_in_what()->set_field(2 /*SCREEN_STATE_CHANGE*/);
-    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
     return config;
 }
 
@@ -175,11 +151,11 @@
 
 TEST(MetricsManagerTest, TestLogSourcesOnConfigUpdate) {
     string app1 = "app1";
-    set<int32_t> app1Uids = {1111, 11111};
+    set<int32_t> app1Uids = {11110, 11111};
     string app2 = "app2";
-    set<int32_t> app2Uids = {2222};
+    set<int32_t> app2Uids = {22220};
     string app3 = "app3";
-    set<int32_t> app3Uids = {3333, 1111};
+    set<int32_t> app3Uids = {33330, 11110};
 
     map<string, set<int32_t>> pkgToUids;
     pkgToUids[app1] = app1Uids;
@@ -205,7 +181,6 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
 
     StatsdConfig config;
-    config.add_allowed_log_source("AID_SYSTEM");
     config.add_allowed_log_source(app1);
     config.add_default_pull_packages("AID_SYSTEM");
     config.add_default_pull_packages("AID_ROOT");
@@ -226,7 +201,6 @@
 
     // Update with new allowed log sources.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
     newConfig.add_allowed_log_source(app2);
     newConfig.add_default_pull_packages("AID_SYSTEM");
     newConfig.add_default_pull_packages("AID_STATSD");
@@ -245,10 +219,9 @@
                                 periodicAlarmMonitor);
     EXPECT_TRUE(metricsManager.isConfigValid());
 
-    EXPECT_THAT(metricsManager.mAllowedUid, ElementsAre(AID_ROOT));
     EXPECT_THAT(metricsManager.mAllowedPkg, ElementsAre(app2));
     EXPECT_THAT(metricsManager.mAllowedLogSources,
-                ContainerEq(unionSet(vector<set<int32_t>>({app2Uids, {AID_ROOT}}))));
+                ContainerEq(unionSet(vector<set<int32_t>>({app2Uids}))));
     const set<int32_t> defaultPullUids = {AID_SYSTEM, AID_STATSD};
     EXPECT_THAT(metricsManager.mDefaultPullUids, ContainerEq(defaultPullUids));
 
@@ -264,6 +237,43 @@
                 UnorderedElementsAreArray(unionSet({defaultPullUids, app2Uids, {AID_ADB}})));
 }
 
+struct MetricsManagerServerFlagParam {
+    string flagValue;
+    string label;
+};
+
+class MetricsManagerTest_SPlus
+    : public ::testing::Test,
+      public ::testing::WithParamInterface<MetricsManagerServerFlagParam> {
+protected:
+    void SetUp() override {
+        if (shouldSkipTest()) {
+            GTEST_SKIP() << skipReason();
+        }
+    }
+
+    bool shouldSkipTest() const {
+        return !isAtLeastS();
+    }
+
+    string skipReason() const {
+        return "Skipping MetricsManagerTest_SPlus because device is not S+";
+    }
+
+    std::optional<string> originalFlagValue;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+        MetricsManagerTest_SPlus, MetricsManagerTest_SPlus,
+        testing::ValuesIn<MetricsManagerServerFlagParam>({
+                // Server flag values
+                {FLAG_TRUE, "ServerFlagTrue"},
+                {FLAG_FALSE, "ServerFlagFalse"},
+        }),
+        [](const testing::TestParamInfo<MetricsManagerTest_SPlus::ParamType>& info) {
+            return info.param.label;
+        });
+
 TEST(MetricsManagerTest, TestCheckLogCredentialsWhitelistedAtom) {
     sp<UidMap> uidMap;
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
@@ -277,7 +287,8 @@
     MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
                                   pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
 
-    LogEvent event(0 /* uid */, 0 /* pid */);
+    const int32_t customAppUid = AID_APP_START + 1;
+    LogEvent event(customAppUid /* uid */, 0 /* pid */);
     CreateNoValuesLogEvent(&event, 10 /* atom id */, 0 /* timestamp */);
     EXPECT_FALSE(metricsManager.checkLogCredentials(event));
 
@@ -294,7 +305,7 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> periodicAlarmMonitor;
 
-    StatsdConfig config = buildGoodConfig();
+    StatsdConfig config = buildGoodConfig(kConfigId);
     config.add_allowed_log_source("AID_SYSTEM");
     config.add_whitelisted_atom_ids(3);
     config.add_whitelisted_atom_ids(4);
@@ -316,6 +327,54 @@
     EXPECT_FALSE(metricsManager.isConfigValid());
 }
 
+TEST_P(MetricsManagerTest_SPlus, TestRestrictedMetricsConfig) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+
+    StatsdConfig config = buildGoodRestrictedConfig();
+    config.add_allowed_log_source("AID_SYSTEM");
+    config.set_restricted_metrics_delegate_package_name("rm");
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    if (isAtLeastU()) {
+        EXPECT_TRUE(metricsManager.isConfigValid());
+    } else {
+        EXPECT_EQ(metricsManager.mInvalidConfigReason,
+                  INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED);
+        ASSERT_FALSE(metricsManager.isConfigValid());
+    }
+}
+
+TEST_P(MetricsManagerTest_SPlus, TestRestrictedMetricsConfigUpdate) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+
+    StatsdConfig config = buildGoodRestrictedConfig();
+    config.add_allowed_log_source("AID_SYSTEM");
+    config.set_restricted_metrics_delegate_package_name("rm");
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    StatsdConfig config2 = buildGoodRestrictedConfig();
+    metricsManager.updateConfig(config, timeBaseSec, timeBaseSec, anomalyAlarmMonitor,
+                                periodicAlarmMonitor);
+
+    if (isAtLeastU()) {
+        EXPECT_TRUE(metricsManager.isConfigValid());
+    } else {
+        EXPECT_EQ(metricsManager.mInvalidConfigReason,
+                  INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_ENABLED);
+        ASSERT_FALSE(metricsManager.isConfigValid());
+    }
+}
+
 TEST(MetricsManagerTest, TestMaxMetricsMemoryKb) {
     sp<UidMap> uidMap;
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
@@ -323,7 +382,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     size_t memoryLimitKb = 8 * 1024;
 
-    StatsdConfig config = buildGoodConfig();
+    StatsdConfig config = buildGoodConfig(kConfigId);
     config.add_allowed_log_source("AID_SYSTEM");
     config.set_max_metrics_memory_kb(memoryLimitKb);
 
@@ -341,7 +400,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     size_t memoryLimitKb = 8 * 1024;
 
-    StatsdConfig config = buildGoodConfig();
+    StatsdConfig config = buildGoodConfig(kConfigId);
     config.add_allowed_log_source("AID_SYSTEM");
     config.set_max_metrics_memory_kb(memoryLimitKb);
 
@@ -371,7 +430,7 @@
     size_t memoryLimitKb = (StatsdStats::kHardMaxMetricsBytesPerConfig / 1024) + 1;
     size_t defaultMemoryLimit = StatsdStats::kDefaultMaxMetricsBytesPerConfig;
 
-    StatsdConfig config = buildGoodConfig();
+    StatsdConfig config = buildGoodConfig(kConfigId);
     config.add_allowed_log_source("AID_SYSTEM");
     config.set_max_metrics_memory_kb(memoryLimitKb);
 
@@ -383,6 +442,91 @@
     EXPECT_TRUE(metricsManager.isConfigValid());
 }
 
+TEST(MetricsManagerTest, TestGetTriggerMemoryKb) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    size_t memoryLimitKb = 8 * 1024;
+
+    StatsdConfig config = buildGoodConfig(kConfigId);
+    config.add_allowed_log_source("AID_SYSTEM");
+    config.set_soft_metrics_memory_kb(memoryLimitKb);
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    EXPECT_EQ(memoryLimitKb, metricsManager.getTriggerGetDataBytes() / 1024);
+    EXPECT_TRUE(metricsManager.isConfigValid());
+}
+
+TEST(MetricsManagerTest, TestGetTriggerMemoryKbOnConfigUpdate) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    size_t memoryLimitKb = 8 * 1024;
+
+    StatsdConfig config = buildGoodConfig(kConfigId);
+    config.add_allowed_log_source("AID_SYSTEM");
+    config.set_soft_metrics_memory_kb(memoryLimitKb);
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    EXPECT_EQ(memoryLimitKb, metricsManager.getTriggerGetDataBytes() / 1024);
+    EXPECT_TRUE(metricsManager.isConfigValid());
+
+    // Update with new memory limit
+    size_t newMemoryLimitKb = 9 * 1024;
+    StatsdConfig newConfig;
+    newConfig.add_allowed_log_source("AID_SYSTEM");
+    newConfig.set_soft_metrics_memory_kb(newMemoryLimitKb);
+
+    metricsManager.updateConfig(newConfig, timeBaseSec, timeBaseSec, anomalyAlarmMonitor,
+                                periodicAlarmMonitor);
+    EXPECT_EQ(newMemoryLimitKb, metricsManager.getTriggerGetDataBytes() / 1024);
+    EXPECT_TRUE(metricsManager.isConfigValid());
+}
+
+TEST(MetricsManagerTest, TestGetTriggerMemoryKbInvalid) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    size_t memoryLimitKb = (StatsdStats::kHardMaxTriggerGetDataBytes / 1024) + 1;
+    size_t defaultMemoryLimit = StatsdStats::kDefaultBytesPerConfigTriggerGetData;
+
+    StatsdConfig config = buildGoodConfig(kConfigId);
+    config.add_allowed_log_source("AID_SYSTEM");
+    config.set_soft_metrics_memory_kb(memoryLimitKb);
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    // Since 10MB + 1B is invalid for the memory limit, we default back to 192KB
+    EXPECT_EQ(defaultMemoryLimit, metricsManager.getTriggerGetDataBytes());
+    EXPECT_TRUE(metricsManager.isConfigValid());
+}
+
+TEST(MetricsManagerTest, TestGetTriggerMemoryKbUnset) {
+    sp<UidMap> uidMap;
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+    size_t defaultMemoryLimit = StatsdStats::kDefaultBytesPerConfigTriggerGetData;
+
+    StatsdConfig config = buildGoodConfig(kConfigId);
+    config.add_allowed_log_source("AID_SYSTEM");
+
+    MetricsManager metricsManager(kConfigKey, config, timeBaseSec, timeBaseSec, uidMap,
+                                  pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor);
+
+    // Since the memory limit is unset, we default back to 192KB
+    EXPECT_EQ(defaultMemoryLimit, metricsManager.getTriggerGetDataBytes());
+    EXPECT_TRUE(metricsManager.isConfigValid());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/SocketListener_test.cpp b/statsd/tests/SocketListener_test.cpp
index 42c084b..cc5475e 100644
--- a/statsd/tests/SocketListener_test.cpp
+++ b/statsd/tests/SocketListener_test.cpp
@@ -55,26 +55,24 @@
 
 }  //  namespace
 
-void generateAtomLogging(const std::shared_ptr<LogEventQueue>& queue,
-                         const std::shared_ptr<LogEventFilter>& filter, int eventCount,
+void generateAtomLogging(LogEventQueue& queue, const LogEventFilter& filter, int eventCount,
                          int startAtomId) {
     // create number of AStatsEvent
     for (int i = 0; i < eventCount; i++) {
         AStatsEventWrapper event(startAtomId + i);
         auto [buf, size] = event.getBuffer();
-        StatsSocketListener::processMessage(buf, size, kTestUid, kTestPid, queue, filter);
+        StatsSocketListener::processStatsEventBuffer(buf, size, kTestUid, kTestPid, queue, filter);
     }
 }
 
-class SocketParseMessageTestNoFiltering : public testing::TestWithParam<bool> {
+class SocketParseMessageTest : public testing::TestWithParam<bool> {
 protected:
-    std::shared_ptr<LogEventQueue> mEventQueue;
-    std::shared_ptr<LogEventFilter> mLogEventFilter;
+    LogEventQueue mEventQueue;
+    LogEventFilter mLogEventFilter;
 
 public:
-    SocketParseMessageTestNoFiltering()
-        : mEventQueue(std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/)),
-          mLogEventFilter(GetParam() ? std::make_shared<LogEventFilter>() : nullptr) {
+    SocketParseMessageTest() : mEventQueue(kEventCount /*buffer limit*/) {
+        mLogEventFilter.setFilteringEnabled(GetParam());
     }
 
     static std::string ToString(testing::TestParamInfo<bool> info) {
@@ -82,100 +80,61 @@
     }
 };
 
-INSTANTIATE_TEST_SUITE_P(SocketParseMessageTestNoFiltering, SocketParseMessageTestNoFiltering,
-                         testing::Bool(), SocketParseMessageTestNoFiltering::ToString);
+INSTANTIATE_TEST_SUITE_P(SocketParseMessageTest, SocketParseMessageTest, testing::Bool(),
+                         SocketParseMessageTest::ToString);
 
-TEST_P(SocketParseMessageTestNoFiltering, TestProcessMessageNoFiltering) {
-    if (GetParam()) {
-        mLogEventFilter->setFilteringEnabled(false);
-    }
+TEST_P(SocketParseMessageTest, TestProcessMessage) {
+    StatsdStats::getInstance().reset();
 
     generateAtomLogging(mEventQueue, mLogEventFilter, kEventCount, kAtomId);
 
+    int64_t lastEventTs = 0;
     // check content of the queue
-    EXPECT_EQ(kEventCount, mEventQueue->mQueue.size());
+    EXPECT_EQ(kEventCount, mEventQueue.mQueue.size());
     for (int i = 0; i < kEventCount; i++) {
-        auto logEvent = mEventQueue->waitPop();
+        auto logEvent = mEventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
-        EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+        EXPECT_EQ(logEvent->isParsedHeaderOnly(), GetParam());
+        lastEventTs = logEvent->GetElapsedTimestampNs();
     }
+
+    EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObserved, kEventCount);
+    EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObservedElapsedNanos, lastEventTs);
 }
 
-TEST_P(SocketParseMessageTestNoFiltering, TestProcessMessageNoFilteringWithEmptySetExplicitSet) {
-    if (GetParam()) {
-        mLogEventFilter->setFilteringEnabled(false);
-        LogEventFilter::AtomIdSet idsList;
-        mLogEventFilter->setAtomIds(idsList, nullptr);
-    }
-
-    generateAtomLogging(mEventQueue, mLogEventFilter, kEventCount, kAtomId);
-
-    // check content of the queue
-    EXPECT_EQ(kEventCount, mEventQueue->mQueue.size());
-    for (int i = 0; i < kEventCount; i++) {
-        auto logEvent = mEventQueue->waitPop();
-        EXPECT_TRUE(logEvent->isValid());
-        EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
-        EXPECT_FALSE(logEvent->isParsedHeaderOnly());
-    }
-}
-
-TEST(SocketParseMessageTest, TestProcessMessageFilterEmptySet) {
-    std::shared_ptr<LogEventQueue> eventQueue =
-            std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
-
-    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
-
-    generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
-
-    // check content of the queue
-    for (int i = 0; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
-        EXPECT_TRUE(logEvent->isValid());
-        EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
-        EXPECT_TRUE(logEvent->isParsedHeaderOnly());
-    }
-}
-
-TEST(SocketParseMessageTest, TestProcessMessageFilterEmptySetExplicitSet) {
-    std::shared_ptr<LogEventQueue> eventQueue =
-            std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
-
-    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
-
+TEST_P(SocketParseMessageTest, TestProcessMessageEmptySetExplicitSet) {
     LogEventFilter::AtomIdSet idsList;
-    logEventFilter->setAtomIds(idsList, nullptr);
-
-    generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
+    mLogEventFilter.setAtomIds(idsList, nullptr);
+    generateAtomLogging(mEventQueue, mLogEventFilter, kEventCount, kAtomId);
 
     // check content of the queue
+    EXPECT_EQ(kEventCount, mEventQueue.mQueue.size());
     for (int i = 0; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = mEventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
-        EXPECT_TRUE(logEvent->isParsedHeaderOnly());
+        EXPECT_EQ(logEvent->isParsedHeaderOnly(), GetParam());
     }
 }
 
 TEST(SocketParseMessageTest, TestProcessMessageFilterCompleteSet) {
-    std::shared_ptr<LogEventQueue> eventQueue =
-            std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
+    LogEventQueue eventQueue(kEventCount /*buffer limit*/);
 
-    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+    LogEventFilter logEventFilter;
 
     LogEventFilter::AtomIdSet idsList;
     for (int i = 0; i < kEventCount; i++) {
         idsList.insert(kAtomId + i);
     }
-    logEventFilter->setAtomIds(idsList, nullptr);
+    logEventFilter.setAtomIds(idsList, nullptr);
 
     generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
 
     // check content of the queue
-    EXPECT_EQ(kEventCount, eventQueue->mQueue.size());
+    EXPECT_EQ(kEventCount, eventQueue.mQueue.size());
     for (int i = 0; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_FALSE(logEvent->isParsedHeaderOnly());
@@ -183,30 +142,29 @@
 }
 
 TEST(SocketParseMessageTest, TestProcessMessageFilterPartialSet) {
-    std::shared_ptr<LogEventQueue> eventQueue =
-            std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
+    LogEventQueue eventQueue(kEventCount /*buffer limit*/);
 
-    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+    LogEventFilter logEventFilter;
 
     LogEventFilter::AtomIdSet idsList;
     for (int i = 0; i < kEventFilteredCount; i++) {
         idsList.insert(kAtomId + i);
     }
-    logEventFilter->setAtomIds(idsList, nullptr);
+    logEventFilter.setAtomIds(idsList, nullptr);
 
     generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
 
     // check content of the queue
-    EXPECT_EQ(kEventCount, eventQueue->mQueue.size());
+    EXPECT_EQ(kEventCount, eventQueue.mQueue.size());
     for (int i = 0; i < kEventFilteredCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_FALSE(logEvent->isParsedHeaderOnly());
     }
 
     for (int i = kEventFilteredCount; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_TRUE(logEvent->isParsedHeaderOnly());
@@ -214,41 +172,40 @@
 }
 
 TEST(SocketParseMessageTest, TestProcessMessageFilterToggle) {
-    std::shared_ptr<LogEventQueue> eventQueue =
-            std::make_shared<LogEventQueue>(kEventCount * 3 /*buffer limit*/);
+    LogEventQueue eventQueue(kEventCount * 3 /*buffer limit*/);
 
-    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+    LogEventFilter logEventFilter;
 
     LogEventFilter::AtomIdSet idsList;
     for (int i = 0; i < kEventFilteredCount; i++) {
         idsList.insert(kAtomId + i);
     }
     // events with ids from kAtomId to kAtomId + kEventFilteredCount should not be skipped
-    logEventFilter->setAtomIds(idsList, nullptr);
+    logEventFilter.setAtomIds(idsList, nullptr);
 
     generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
 
-    logEventFilter->setFilteringEnabled(false);
+    logEventFilter.setFilteringEnabled(false);
     // since filtering is disabled - events with any ids should not be skipped
     // will generate events with ids [kAtomId + kEventCount, kAtomId + kEventCount * 2]
     generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId + kEventCount);
 
-    logEventFilter->setFilteringEnabled(true);
+    logEventFilter.setFilteringEnabled(true);
     LogEventFilter::AtomIdSet idsList2;
     for (int i = kEventFilteredCount; i < kEventCount; i++) {
         idsList2.insert(kAtomId + kEventCount * 2 + i);
     }
     // events with idsList2 ids should not be skipped
-    logEventFilter->setAtomIds(idsList2, nullptr);
+    logEventFilter.setAtomIds(idsList2, nullptr);
 
     // will generate events with ids [kAtomId + kEventCount * 2, kAtomId + kEventCount * 3]
     generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId + kEventCount * 2);
 
     // check content of the queue
-    EXPECT_EQ(kEventCount * 3, eventQueue->mQueue.size());
+    EXPECT_EQ(kEventCount * 3, eventQueue.mQueue.size());
     // events with ids from kAtomId to kAtomId + kEventFilteredCount should not be skipped
     for (int i = 0; i < kEventFilteredCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_FALSE(logEvent->isParsedHeaderOnly());
@@ -256,7 +213,7 @@
 
     // all events above kAtomId + kEventFilteredCount to kAtomId + kEventCount should be skipped
     for (int i = kEventFilteredCount; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
         EXPECT_TRUE(logEvent->isParsedHeaderOnly());
@@ -265,7 +222,7 @@
     // events with ids [kAtomId + kEventCount, kAtomId + kEventCount * 2] should not be skipped
     // since wiltering was disabled at that time
     for (int i = 0; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + kEventCount + i, logEvent->GetTagId());
         EXPECT_FALSE(logEvent->isParsedHeaderOnly());
@@ -274,7 +231,7 @@
     // first half events with ids [kAtomId + kEventCount * 2, kAtomId + kEventCount * 3]
     // should be skipped
     for (int i = 0; i < kEventFilteredCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + kEventCount * 2 + i, logEvent->GetTagId());
         EXPECT_TRUE(logEvent->isParsedHeaderOnly());
@@ -283,7 +240,7 @@
     // second half events with ids [kAtomId + kEventCount * 2, kAtomId + kEventCount * 3]
     // should be processed
     for (int i = kEventFilteredCount; i < kEventCount; i++) {
-        auto logEvent = eventQueue->waitPop();
+        auto logEvent = eventQueue.waitPop();
         EXPECT_TRUE(logEvent->isValid());
         EXPECT_EQ(kAtomId + kEventCount * 2 + i, logEvent->GetTagId());
         EXPECT_FALSE(logEvent->isParsedHeaderOnly());
diff --git a/statsd/tests/StatsLogProcessor_test.cpp b/statsd/tests/StatsLogProcessor_test.cpp
index 27cef6b..61e861f 100644
--- a/statsd/tests/StatsLogProcessor_test.cpp
+++ b/statsd/tests/StatsLogProcessor_test.cpp
@@ -30,6 +30,7 @@
 #include "statslog_statsdtest.h"
 #include "storage/StorageManager.h"
 #include "tests/statsd_test_util.h"
+#include "utils/DbUtils.h"
 
 using namespace android;
 using namespace testing;
@@ -53,20 +54,29 @@
  */
 class MockMetricsManager : public MetricsManager {
 public:
-    MockMetricsManager()
-        : MetricsManager(ConfigKey(1, 12345), StatsdConfig(), 1000, 1000, new UidMap(),
+    MockMetricsManager(ConfigKey configKey = ConfigKey(1, 12345))
+        : MetricsManager(configKey, StatsdConfig(), 1000, 1000, new UidMap(),
                          new StatsPullerManager(),
-                         new AlarmMonitor(10,
-                                          [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
-                                          [](const shared_ptr<IStatsCompanionService>&) {}),
-                         new AlarmMonitor(10,
-                                          [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
-                                          [](const shared_ptr<IStatsCompanionService>&) {})) {
+                         new AlarmMonitor(
+                                 10, [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+                                 [](const shared_ptr<IStatsCompanionService>&) {}),
+                         new AlarmMonitor(
+                                 10, [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+                                 [](const shared_ptr<IStatsCompanionService>&) {})) {
     }
 
     MOCK_METHOD0(byteSize, size_t());
 
     MOCK_METHOD1(dropData, void(const int64_t dropTimeNs));
+
+    MOCK_METHOD(void, onLogEvent, (const LogEvent& event), (override));
+
+    MOCK_METHOD(void, onDumpReport,
+                (const int64_t dumpTimeNs, const int64_t wallClockNs,
+                 const bool include_current_partial_bucket, const bool erase_data,
+                 const DumpLatency dumpLatency, std::set<string>* str_set,
+                 android::util::ProtoOutputStream* protoOutput),
+                (override));
 };
 
 TEST(StatsLogProcessorTest, TestRateLimitByteSize) {
@@ -78,7 +88,9 @@
     StatsLogProcessor p(
             m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
             [](const ConfigKey& key) { return true; },
-            [](const int&, const vector<int64_t>&) { return true; }, nullptr);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {},
+            std::make_shared<LogEventFilter>());
 
     MockMetricsManager mockMetricsManager;
 
@@ -103,7 +115,9 @@
                 broadcastCount++;
                 return true;
             },
-            [](const int&, const vector<int64_t>&) { return true; }, nullptr);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {},
+            std::make_shared<LogEventFilter>());
 
     MockMetricsManager mockMetricsManager;
 
@@ -136,7 +150,9 @@
                 broadcastCount++;
                 return true;
             },
-            [](const int&, const vector<int64_t>&) { return true; }, nullptr);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {},
+            std::make_shared<LogEventFilter>());
 
     MockMetricsManager mockMetricsManager;
 
@@ -155,7 +171,6 @@
 
 StatsdConfig MakeConfig(bool includeMetric) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     if (includeMetric) {
         auto appCrashMatcher = CreateProcessCrashAtomMatcher();
@@ -168,6 +183,44 @@
     return config;
 }
 
+StatsdConfig makeRestrictedConfig(bool includeMetric = false) {
+    StatsdConfig config;
+    config.set_restricted_metrics_delegate_package_name("delegate");
+
+    if (includeMetric) {
+        auto appCrashMatcher = CreateProcessCrashAtomMatcher();
+        *config.add_atom_matcher() = appCrashMatcher;
+        auto eventMetric = config.add_event_metric();
+        eventMetric->set_id(StringToId("EventAppCrashes"));
+        eventMetric->set_what(appCrashMatcher.id());
+    }
+    return config;
+}
+
+class MockRestrictedMetricsManager : public MetricsManager {
+public:
+    MockRestrictedMetricsManager(ConfigKey configKey = ConfigKey(1, 12345))
+        : MetricsManager(configKey, makeRestrictedConfig(), 1000, 1000, new UidMap(),
+                         new StatsPullerManager(),
+                         new AlarmMonitor(
+                                 10, [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+                                 [](const shared_ptr<IStatsCompanionService>&) {}),
+                         new AlarmMonitor(
+                                 10, [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+                                 [](const shared_ptr<IStatsCompanionService>&) {})) {
+    }
+
+    MOCK_METHOD(void, onLogEvent, (const LogEvent& event), (override));
+    MOCK_METHOD(void, onDumpReport,
+                (const int64_t dumpTimeNs, const int64_t wallClockNs,
+                 const bool include_current_partial_bucket, const bool erase_data,
+                 const DumpLatency dumpLatency, std::set<string>* str_set,
+                 android::util::ProtoOutputStream* protoOutput),
+                (override));
+    MOCK_METHOD(size_t, byteSize, (), (override));
+    MOCK_METHOD(void, flushRestrictedData, (), (override));
+};
+
 TEST(StatsLogProcessorTest, TestUidMapHasSnapshot) {
     ConfigKey key(3, 4);
     StatsdConfig config = MakeConfig(true);
@@ -190,7 +243,8 @@
                 broadcastCount++;
                 return true;
             },
-            [](const int&, const vector<int64_t>&) { return true; }, mockLogEventFilter);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &p)).Times(1);
@@ -232,7 +286,8 @@
                 broadcastCount++;
                 return true;
             },
-            [](const int&, const vector<int64_t>&) { return true; }, mockLogEventFilter);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &p)).Times(1);
@@ -256,7 +311,6 @@
     auto annotation = config.add_annotation();
     annotation->set_field_int64(1);
     annotation->set_field_int32(2);
-    config.add_allowed_log_source("AID_ROOT");
 
     sp<UidMap> m = new UidMap();
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
@@ -272,7 +326,8 @@
                 broadcastCount++;
                 return true;
             },
-            [](const int&, const vector<int64_t>&) { return true; }, mockLogEventFilter);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &p)).Times(1);
@@ -295,7 +350,6 @@
 TEST(StatsLogProcessorTest, TestOnDumpReportEraseData) {
     // Setup a simple config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = wakelockAcquireMatcher;
 
@@ -355,7 +409,8 @@
     StatsLogProcessor p(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
             [](const ConfigKey& key) { return true; },
-            [](const int&, const vector<int64_t>&) { return true; }, mockLogEventFilter);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &p)).Times(3);
@@ -389,7 +444,8 @@
     StatsLogProcessor p(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
             [](const ConfigKey& key) { return true; },
-            [](const int&, const vector<int64_t>&) { return true; }, mockLogEventFilter);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(CreateAtomIdSetDefault(), &p)).Times(1);
     // atom used by matcher defined in MakeConfig() API
@@ -410,7 +466,8 @@
     EXPECT_EQ(0, StatsdStats::getInstance().mIceBox.size());
 
     StatsdConfig invalidConfig = MakeConfig(true);
-    invalidConfig.clear_allowed_log_source();
+    auto invalidCountMetric = invalidConfig.add_count_metric();
+    invalidCountMetric->set_what(0);
     p.OnConfigUpdated(0, key, invalidConfig);
     EXPECT_EQ(0, p.mMetricsManagers.size());
     // The current configs should not contain the invalid config.
@@ -429,7 +486,6 @@
     StatsdConfig config1;
     int64_t cfgId1 = 12341;
     config1.set_id(cfgId1);
-    config1.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     *config1.add_atom_matcher() = wakelockAcquireMatcher;
 
@@ -451,7 +507,6 @@
     StatsdConfig config2;
     int64_t cfgId2 = 12342;
     config2.set_id(cfgId2);
-    config2.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     *config2.add_atom_matcher() = wakelockAcquireMatcher;
 
     long metricId3 = 1234561;
@@ -480,7 +535,6 @@
     StatsdConfig config3;
     int64_t cfgId3 = 12343;
     config3.set_id(cfgId3);
-    config3.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     *config3.add_atom_matcher() = wakelockAcquireMatcher;
 
     long metricId5 = 1234565;
@@ -534,7 +588,7 @@
                                               activeConfigs.end());
                 return true;
             },
-            mockLogEventFilter);
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     // config1,config2,config3 use the same atom
     const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config1);
@@ -780,7 +834,6 @@
 
     StatsdConfig config1;
     config1.set_id(12341);
-    config1.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     *config1.add_atom_matcher() = wakelockAcquireMatcher;
 
@@ -904,7 +957,6 @@
     // Metric 2: Always active
     StatsdConfig config1;
     config1.set_id(12341);
-    config1.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     *config1.add_atom_matcher() = wakelockAcquireMatcher;
@@ -1305,7 +1357,6 @@
     // Metric 2: Always active
     StatsdConfig config1;
     config1.set_id(12341);
-    config1.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     *config1.add_atom_matcher() = wakelockAcquireMatcher;
@@ -1569,7 +1620,6 @@
     // Metric 3: Always active
     StatsdConfig config1;
     config1.set_id(configId);
-    config1.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     auto jobStartMatcher = CreateStartScheduledJobAtomMatcher();
@@ -1624,8 +1674,8 @@
 
     // Send the config.
     const sp<UidMap> uidMap = new UidMap();
-    const shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr, nullptr);
+    const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+            uidMap, /* queue */ nullptr, std::make_shared<LogEventFilter>());
     string serialized = config1.SerializeAsString();
     service->addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()});
 
@@ -1816,7 +1866,8 @@
     StatsLogProcessor p(
             m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
             [](const ConfigKey& key) { return true; },
-            [](const int&, const vector<int64_t>&) { return true; }, mockLogEventFilter);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     Expectation filterSetFalse =
             EXPECT_CALL(*mockLogEventFilter, setFilteringEnabled(false)).Times(1);
@@ -2112,6 +2163,202 @@
     EXPECT_EQ(output.reports(0).data_corrupted_reason(1), DATA_CORRUPTED_SOCKET_LOSS);
 }
 
+class StatsLogProcessorTestRestricted : public Test {
+protected:
+    const ConfigKey mConfigKey = ConfigKey(1, 12345);
+    void SetUp() override {
+        if (!isAtLeastU()) {
+            GTEST_SKIP();
+        }
+    }
+    void TearDown() override {
+        if (!isAtLeastU()) {
+            GTEST_SKIP();
+        }
+        FlagProvider::getInstance().resetOverrides();
+        StorageManager::deleteAllFiles(STATS_DATA_DIR);
+        dbutils::deleteDb(mConfigKey);
+    }
+};
+
+TEST_F(StatsLogProcessorTestRestricted, TestInconsistentRestrictedMetricsConfigUpdate) {
+    ConfigKey key(3, 4);
+    StatsdConfig config = makeRestrictedConfig(true);
+    config.set_restricted_metrics_delegate_package_name("rm_package");
+
+    sp<UidMap> m = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    UidData uidData;
+    *uidData.add_app_info() = createApplicationInfo(/*uid*/ 1, /*version*/ 1, "v1", "p1");
+    *uidData.add_app_info() = createApplicationInfo(/*uid*/ 2, /*version*/ 2, "v2", "p2");
+    m->updateMap(1, uidData);
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> subscriberAlarmMonitor;
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+            [](const ConfigKey& key) { return true; },
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+    // new newConfig will be the same as config
+    const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &p)).Times(2);
+
+    p.OnConfigUpdated(0, key, config);
+
+    EXPECT_EQ(1, p.mMetricsManagers.size());
+    EXPECT_NE(p.mMetricsManagers.find(key), p.mMetricsManagers.end());
+    sp<MetricsManager> oldMetricsManager = p.mMetricsManagers.find(key)->second;
+
+    StatsdConfig newConfig = makeRestrictedConfig(true);
+    newConfig.clear_restricted_metrics_delegate_package_name();
+    p.OnConfigUpdated(/*timestampNs=*/0, key, newConfig);
+
+    ASSERT_NE(p.mMetricsManagers.find(key)->second, oldMetricsManager);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, TestRestrictedLogEventNotPassed) {
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            /*timeBaseNs=*/1, /*currentTimeNs=*/1, StatsdConfig(), mConfigKey);
+    ConfigKey key(3, 4);
+    sp<MockMetricsManager> metricsManager = new MockMetricsManager(mConfigKey);
+    EXPECT_CALL(*metricsManager, onLogEvent).Times(0);
+
+    processor->mMetricsManagers[mConfigKey] = metricsManager;
+    EXPECT_FALSE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+    unique_ptr<LogEvent> event = CreateRestrictedLogEvent(123);
+    EXPECT_TRUE(event->isValid());
+    EXPECT_TRUE(event->isRestricted());
+    processor->OnLogEvent(event.get());
+}
+
+TEST_F(StatsLogProcessorTestRestricted, TestRestrictedLogEventPassed) {
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            /*timeBaseNs=*/1, /*currentTimeNs=*/1, StatsdConfig(), mConfigKey);
+    sp<MockRestrictedMetricsManager> metricsManager = new MockRestrictedMetricsManager(mConfigKey);
+    EXPECT_CALL(*metricsManager, onLogEvent).Times(1);
+
+    processor->mMetricsManagers[mConfigKey] = metricsManager;
+    EXPECT_TRUE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+    unique_ptr<LogEvent> event = CreateRestrictedLogEvent(123);
+    EXPECT_TRUE(event->isValid());
+    EXPECT_TRUE(event->isRestricted());
+    processor->OnLogEvent(event.get());
+}
+
+TEST_F(StatsLogProcessorTestRestricted, RestrictedMetricsManagerOnDumpReportNotCalled) {
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            /*timeBaseNs=*/1, /*currentTimeNs=*/1, makeRestrictedConfig(/*includeMetric=*/true),
+            mConfigKey);
+    sp<MockRestrictedMetricsManager> metricsManager = new MockRestrictedMetricsManager(mConfigKey);
+    EXPECT_CALL(*metricsManager, onDumpReport).Times(0);
+
+    processor->mMetricsManagers[mConfigKey] = metricsManager;
+    EXPECT_TRUE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+    vector<uint8_t> buffer;
+    processor->onConfigMetricsReportLocked(mConfigKey, /*dumpTimeStampNs=*/1, /*wallClockNs=*/0,
+                                           /*include_current_partial_bucket=*/true,
+                                           /*erase_data=*/true, GET_DATA_CALLED, FAST,
+                                           /*dataSavedToDisk=*/true, &buffer);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, RestrictedMetricFlushIfReachMemoryLimit) {
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            /*timeBaseNs=*/1, /*currentTimeNs=*/1, makeRestrictedConfig(/*includeMetric=*/true),
+            mConfigKey);
+    sp<MockRestrictedMetricsManager> metricsManager = new MockRestrictedMetricsManager(mConfigKey);
+    EXPECT_CALL(*metricsManager, flushRestrictedData).Times(1);
+    EXPECT_CALL(*metricsManager, byteSize)
+            .Times(1)
+            .WillOnce(Return(StatsdStats::kBytesPerRestrictedConfigTriggerFlush + 1));
+
+    processor->mMetricsManagers[mConfigKey] = metricsManager;
+    EXPECT_TRUE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+    processor->flushIfNecessaryLocked(mConfigKey, *metricsManager);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, RestrictedMetricNotFlushIfNotReachMemoryLimit) {
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            /*timeBaseNs=*/1, /*currentTimeNs=*/1, makeRestrictedConfig(/*includeMetric=*/true),
+            mConfigKey);
+    sp<MockRestrictedMetricsManager> metricsManager = new MockRestrictedMetricsManager(mConfigKey);
+    EXPECT_CALL(*metricsManager, flushRestrictedData).Times(0);
+    EXPECT_CALL(*metricsManager, byteSize)
+            .Times(1)
+            .WillOnce(Return(StatsdStats::kBytesPerRestrictedConfigTriggerFlush - 1));
+
+    processor->mMetricsManagers[mConfigKey] = metricsManager;
+    EXPECT_TRUE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+    processor->flushIfNecessaryLocked(mConfigKey, *metricsManager);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, NonRestrictedMetricsManagerOnDumpReportCalled) {
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            /*timeBaseNs=*/1, /*currentTimeNs=*/1, MakeConfig(/*includeMetric=*/true), mConfigKey);
+    sp<MockMetricsManager> metricsManager = new MockMetricsManager(mConfigKey);
+    EXPECT_CALL(*metricsManager, onDumpReport).Times(1);
+
+    processor->mMetricsManagers[mConfigKey] = metricsManager;
+    EXPECT_FALSE(processor->mMetricsManagers[mConfigKey]->hasRestrictedMetricsDelegate());
+
+    vector<uint8_t> buffer;
+    processor->onConfigMetricsReportLocked(mConfigKey, /*dumpTimeStampNs=*/1, /*wallClockNs=*/0,
+                                           /*include_current_partial_bucket=*/true,
+                                           /*erase_data=*/true, GET_DATA_CALLED, FAST,
+                                           /*dataSavedToDisk=*/true, &buffer);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, RestrictedMetricOnDumpReportEmpty) {
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            /*timeBaseNs=*/1, /*currentTimeNs=*/1, makeRestrictedConfig(/*includeMetric=*/true),
+            mConfigKey);
+    ProtoOutputStream proto;
+    processor->onDumpReport(mConfigKey, /*dumpTimeStampNs=*/1, /*wallClockNs=*/2,
+                            /*include_current_partial_bucket=*/true, /*erase_data=*/true,
+                            DEVICE_SHUTDOWN, FAST, &proto);
+    ASSERT_EQ(proto.size(), 0);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, NonRestrictedMetricOnDumpReportNotEmpty) {
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            /*timeBaseNs=*/1, /*currentTimeNs=*/1, MakeConfig(/*includeMetric=*/true), mConfigKey);
+
+    ProtoOutputStream proto;
+    processor->onDumpReport(mConfigKey, /*dumpTimeStampNs=*/1, /*wallClockNs=*/2,
+                            /*include_current_partial_bucket=*/true, /*erase_data=*/true,
+                            DEVICE_SHUTDOWN, FAST, &proto);
+    ASSERT_NE(proto.size(), 0);
+}
+
+TEST_F(StatsLogProcessorTestRestricted, RestrictedMetricNotWriteToDisk) {
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            /*timeBaseNs=*/1, /*currentTimeNs=*/1, makeRestrictedConfig(/*includeMetric=*/true),
+            mConfigKey);
+
+    processor->WriteDataToDiskLocked(mConfigKey, /*timestampNs=*/0, /*wallClockNs=*/0,
+                                     CONFIG_UPDATED, FAST);
+
+    ASSERT_FALSE(StorageManager::hasConfigMetricsReport(mConfigKey));
+}
+
+TEST_F(StatsLogProcessorTestRestricted, NonRestrictedMetricWriteToDisk) {
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            /*timeBaseNs=*/1, /*currentTimeNs=*/1, MakeConfig(true), mConfigKey);
+
+    processor->WriteDataToDiskLocked(mConfigKey, /*timestampNs=*/0, /*wallClockNs=*/0,
+                                     CONFIG_UPDATED, FAST);
+
+    ASSERT_TRUE(StorageManager::hasConfigMetricsReport(mConfigKey));
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/StatsService_test.cpp b/statsd/tests/StatsService_test.cpp
index 5bc2f74..ad85639 100644
--- a/statsd/tests/StatsService_test.cpp
+++ b/statsd/tests/StatsService_test.cpp
@@ -43,7 +43,6 @@
 
 StatsdConfig CreateStatsdConfig(const GaugeMetric::SamplingType samplingType) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");     // LogEvent defaults to UID of root.
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
     auto atomMatcher = CreateSimpleAtomMatcher("TestMatcher", ATOM_TAG);
     *config.add_atom_matcher() = atomMatcher;
@@ -68,7 +67,7 @@
 TEST(StatsServiceTest, TestAddConfig_simple) {
     const sp<UidMap> uidMap = new UidMap();
     shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
-            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
+            uidMap, /* queue */ nullptr, std::make_shared<LogEventFilter>());
     const int kConfigKey = 12345;
     const int kCallingUid = 123;
     StatsdConfig config;
@@ -87,7 +86,7 @@
 TEST(StatsServiceTest, TestAddConfig_empty) {
     const sp<UidMap> uidMap = new UidMap();
     shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
-            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
+            uidMap, /* queue */ nullptr, std::make_shared<LogEventFilter>());
     string serialized = "";
     const int kConfigKey = 12345;
     const int kCallingUid = 123;
@@ -103,7 +102,7 @@
 TEST(StatsServiceTest, TestAddConfig_invalid) {
     const sp<UidMap> uidMap = new UidMap();
     shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
-            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
+            uidMap, /* queue */ nullptr, std::make_shared<LogEventFilter>());
     string serialized = "Invalid config!";
 
     EXPECT_FALSE(
@@ -122,7 +121,7 @@
 
     const sp<UidMap> uidMap = new UidMap();
     shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
-            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
+            uidMap, /* queue */ nullptr, std::make_shared<LogEventFilter>());
     service->mEngBuild = true;
 
     // "-1"
@@ -162,7 +161,7 @@
 
     shared_ptr<StatsService> createStatsService() override {
         return SharedRefBase::make<StatsService>(new UidMap(), /*queue=*/nullptr,
-                                                 /*LogEventFilter=*/nullptr,
+                                                 std::make_shared<LogEventFilter>(),
                                                  /*initEventDelaySecs=*/kInitDelaySec);
     }
 };
diff --git a/statsd/tests/UidMap_test.cpp b/statsd/tests/UidMap_test.cpp
index 2304c74..9e5d7f0 100644
--- a/statsd/tests/UidMap_test.cpp
+++ b/statsd/tests/UidMap_test.cpp
@@ -52,7 +52,7 @@
 const vector<string> kApps{kApp1, kApp2, kApp3};
 const vector<string> kInstallers{"", "", "com.android.vending"};
 const vector<vector<uint8_t>> kCertificateHashes{{'a', 'z'}, {'b', 'c'}, {'d', 'e'}};
-const vector<bool> kDeleted(3, false);
+const vector<uint8_t> kDeleted(3, false);
 
 void sendPackagesToStatsd(shared_ptr<StatsService> service, const vector<int32_t>& uids,
                           const vector<int64_t>& versions, const vector<string>& versionStrings,
@@ -108,7 +108,9 @@
     StatsLogProcessor p(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
             [](const ConfigKey& key) { return true; },
-            [](const int&, const vector<int64_t>&) { return true; }, nullptr);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {},
+            std::make_shared<LogEventFilter>());
 
     std::unique_ptr<LogEvent> addEvent = CreateIsolatedUidChangedEvent(
             1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 1 /*is_create*/);
@@ -125,7 +127,7 @@
 TEST(UidMapTest, TestUpdateMap) {
     const sp<UidMap> uidMap = new UidMap();
     const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
-            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
+            uidMap, /* queue */ nullptr, std::make_shared<LogEventFilter>());
     sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
                          kCertificateHashes);
 
@@ -157,7 +159,7 @@
 TEST(UidMapTest, TestUpdateMapMultiple) {
     const sp<UidMap> uidMap = new UidMap();
     const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
-            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
+            uidMap, /* queue */ nullptr, std::make_shared<LogEventFilter>());
     sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
                          kCertificateHashes);
 
@@ -201,7 +203,7 @@
 TEST(UidMapTest, TestRemoveApp) {
     const sp<UidMap> uidMap = new UidMap();
     const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
-            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
+            uidMap, /* queue */ nullptr, std::make_shared<LogEventFilter>());
     sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
                          kCertificateHashes);
 
@@ -212,7 +214,7 @@
     std::set<string> name_set = uidMap->getAppNamesFromUid(1000, true /* returnNormalized */);
     EXPECT_THAT(name_set, UnorderedElementsAre(kApp2));
 
-    vector<bool> deleted(kDeleted);
+    vector<uint8_t> deleted(kDeleted);
     deleted[0] = true;
     vector<PackageInfo> expectedPackageInfos =
             buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
@@ -258,7 +260,7 @@
 TEST(UidMapTest, TestUpdateApp) {
     const sp<UidMap> uidMap = new UidMap();
     const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
-            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
+            uidMap, /* queue */ nullptr, std::make_shared<LogEventFilter>());
     sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
                          kCertificateHashes);
 
@@ -297,7 +299,7 @@
     versionStrings[0] = "v40";
     vector<string> apps = concatenate(kApps, {"NeW_aPP1_NAmE", "NeW_aPP1_NAmE"});
     vector<string> installers = concatenate(kInstallers, {"com.android.vending", "new_installer"});
-    vector<bool> deleted = concatenate(kDeleted, {false, false});
+    vector<uint8_t> deleted = concatenate(kDeleted, {false, false});
     vector<vector<uint8_t>> certHashes = concatenate(kCertificateHashes, {{'a'}, {'b'}});
     vector<PackageInfo> expectedPackageInfos =
             buildPackageInfos(apps, uids, versions, versionStrings, installers, certHashes, deleted,
@@ -530,7 +532,7 @@
         : config1(1, StringToId("config1")),
           uidMap(new UidMap()),
           service(SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr,
-                                                    /* LogEventFilter */ nullptr)) {
+                                                    std::make_shared<LogEventFilter>())) {
     }
 
     void SetUp() override {
diff --git a/statsd/tests/anomaly/AlarmTracker_test.cpp b/statsd/tests/anomaly/AlarmTracker_test.cpp
index 64ea219..9a4863b 100644
--- a/statsd/tests/anomaly/AlarmTracker_test.cpp
+++ b/statsd/tests/anomaly/AlarmTracker_test.cpp
@@ -17,8 +17,12 @@
 #include <gtest/gtest.h>
 #include <log/log_time.h>
 #include <stdio.h>
+
 #include <vector>
 
+#include "src/subscriber/SubscriberReporter.h"
+#include "tests/statsd_test_util.h"
+
 using namespace testing;
 using android::sp;
 using std::set;
@@ -32,7 +36,11 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, 12345);
+namespace {
+const int kConfigUid = 0;
+const int kConfigId = 12345;
+const ConfigKey kConfigKey(kConfigUid, kConfigId);
+}  // anonymous namespace
 
 TEST(AlarmTrackerTest, TestTriggerTimestamp) {
     sp<AlarmMonitor> subscriberAlarmMonitor =
@@ -86,6 +94,115 @@
     EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime);
 }
 
+TEST(AlarmTrackerTest, TestProbabilityOfInforming) {
+    // Initiating StatsdStats at the start of this test, so it doesn't call rand() during the test
+    StatsdStats::getInstance();
+    srand(/*commonly used seed=*/0);
+    sp<AlarmMonitor> subscriberAlarmMonitor = new AlarmMonitor(
+            100, [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
+            [](const shared_ptr<IStatsCompanionService>&) {});
+    int broadcastSubRandId = 1, broadcastSubAlwaysId = 2, broadcastSubNeverId = 3;
+
+    int64_t startMillis = 100000000 * MS_PER_SEC;
+    uint64_t currentTimeSec = startMillis / MS_PER_SEC + 15 + 60 * 60;
+
+    // Alarm with probability of informing set to 0.5
+    Alarm alarmRand = createAlarm("alarmRand", /*offsetMillis=*/15 * MS_PER_SEC,
+                                  /*periodMillis=*/60 * 60 * MS_PER_SEC);
+    alarmRand.set_probability_of_informing(0.5);
+    AlarmTracker trackerRand(startMillis, startMillis, alarmRand, kConfigKey,
+                             subscriberAlarmMonitor);
+    Subscription subRand = createSubscription("subRand", /*rule_type=*/Subscription::ALARM,
+                                              /*rule_id=*/alarmRand.id());
+    subRand.mutable_broadcast_subscriber_details()->set_subscriber_id(broadcastSubRandId);
+    trackerRand.addSubscription(subRand);
+
+    // Alarm with probability of informing set to 1.1 (always; set by default)
+    Alarm alarmAlways = createAlarm("alarmAlways", /*offsetMillis=*/15 * MS_PER_SEC,
+                                    /*periodMillis=*/60 * 60 * MS_PER_SEC);
+    AlarmTracker trackerAlways(startMillis, startMillis, alarmAlways, kConfigKey,
+                               subscriberAlarmMonitor);
+    Subscription subAlways = createSubscription("subAlways", /*rule_type=*/Subscription::ALARM,
+                                                /*rule_id=*/alarmAlways.id());
+    subAlways.mutable_broadcast_subscriber_details()->set_subscriber_id(broadcastSubAlwaysId);
+    trackerAlways.addSubscription(subAlways);
+
+    // Alarm with probability of informing set to -0.1 (never)
+    Alarm alarmNever = createAlarm("alarmNever", /*offsetMillis=*/15 * MS_PER_SEC,
+                                   /*periodMillis=*/60 * 60 * MS_PER_SEC);
+    alarmNever.set_probability_of_informing(-0.1);
+    AlarmTracker trackerNever(startMillis, startMillis, alarmNever, kConfigKey,
+                              subscriberAlarmMonitor);
+    Subscription subNever = createSubscription("subNever", /*rule_type=*/Subscription::ALARM,
+                                               /*rule_id=*/alarmNever.id());
+    subNever.mutable_broadcast_subscriber_details()->set_subscriber_id(broadcastSubNeverId);
+    trackerNever.addSubscription(subNever);
+
+    std::unordered_set<sp<const InternalAlarm>, SpHash<InternalAlarm>> firedAlarmSet =
+            subscriberAlarmMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec));
+    ASSERT_EQ(firedAlarmSet.size(), 3u);
+
+    int alarmRandCount = 0, alarmAlwaysCount = 0;
+    // The binder calls here will happen synchronously because they are in-process.
+    shared_ptr<MockPendingIntentRef> randBroadcast =
+            SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*randBroadcast,
+                sendSubscriberBroadcast(kConfigUid, kConfigId, subRand.id(), alarmRand.id(), _, _))
+            .Times(3)
+            .WillRepeatedly([&alarmRandCount] {
+                alarmRandCount++;
+                return Status::ok();
+            });
+
+    shared_ptr<MockPendingIntentRef> alwaysBroadcast =
+            SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*alwaysBroadcast, sendSubscriberBroadcast(kConfigUid, kConfigId, subAlways.id(),
+                                                          alarmAlways.id(), _, _))
+            .Times(10)
+            .WillRepeatedly([&alarmAlwaysCount] {
+                alarmAlwaysCount++;
+                return Status::ok();
+            });
+
+    shared_ptr<MockPendingIntentRef> neverBroadcast =
+            SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*neverBroadcast, sendSubscriberBroadcast(kConfigUid, kConfigId, subNever.id(),
+                                                         alarmNever.id(), _, _))
+            .Times(0);
+
+    SubscriberReporter::getInstance().setBroadcastSubscriber(kConfigKey, broadcastSubRandId,
+                                                             randBroadcast);
+    SubscriberReporter::getInstance().setBroadcastSubscriber(kConfigKey, broadcastSubAlwaysId,
+                                                             alwaysBroadcast);
+    SubscriberReporter::getInstance().setBroadcastSubscriber(kConfigKey, broadcastSubNeverId,
+                                                             neverBroadcast);
+    // Trying to inform the subscription 10x.
+    // Deterministic sequence for trackerRand:
+    // 0.96, 0.95, 0.95, 0.94, 0.43, 0.92, 0.92, 0.41, 0.39, 0.88
+    for (size_t i = 0; i < 10; i++) {
+        trackerRand.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet);
+        if (i <= 3) {
+            EXPECT_EQ(alarmRandCount, 0);
+        } else if (i >= 4 && i <= 6) {
+            EXPECT_EQ(alarmRandCount, 1);
+        } else if (i == 7) {
+            EXPECT_EQ(alarmRandCount, 2);
+        } else {
+            EXPECT_EQ(alarmRandCount, 3);
+        }
+        trackerAlways.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet);
+        EXPECT_EQ(alarmAlwaysCount, i + 1);
+        trackerNever.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet);
+
+        currentTimeSec = startMillis / MS_PER_SEC + 15 + (i + 2) * 60 * 60;
+        firedAlarmSet =
+                subscriberAlarmMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec));
+    }
+    SubscriberReporter::getInstance().unsetBroadcastSubscriber(kConfigKey, broadcastSubRandId);
+    SubscriberReporter::getInstance().unsetBroadcastSubscriber(kConfigKey, broadcastSubAlwaysId);
+    SubscriberReporter::getInstance().unsetBroadcastSubscriber(kConfigKey, broadcastSubNeverId);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/anomaly/AnomalyTracker_test.cpp b/statsd/tests/anomaly/AnomalyTracker_test.cpp
index 0cc8af1..827f8f5 100644
--- a/statsd/tests/anomaly/AnomalyTracker_test.cpp
+++ b/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -20,10 +20,12 @@
 
 #include <vector>
 
+#include "src/subscriber/SubscriberReporter.h"
 #include "tests/statsd_test_util.h"
 
 using namespace testing;
 using android::sp;
+using ::ndk::SharedRefBase;
 using std::set;
 using std::unordered_map;
 using std::vector;
@@ -34,7 +36,11 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, 12345);
+namespace {
+const int kConfigUid = 0;
+const int kConfigId = 12345;
+const ConfigKey kConfigKey(kConfigUid, kConfigId);
+}  // anonymous namespace
 
 MetricDimensionKey getMockMetricDimensionKey(int key, string value) {
     int pos[] = {key, 0, 0};
@@ -68,8 +74,7 @@
 }
 
 // Returns true if keys in trueList are detected as anomalies and keys in falseList are not.
-bool detectAnomaliesPass(AnomalyTracker& tracker,
-                         const int64_t& bucketNum,
+bool detectAnomaliesPass(AnomalyTracker& tracker, int64_t bucketNum,
                          const std::shared_ptr<DimToValMap>& currentBucket,
                          const std::set<const MetricDimensionKey>& trueList,
                          const std::set<const MetricDimensionKey>& falseList) {
@@ -87,10 +92,8 @@
 }
 
 // Calls tracker.detectAndDeclareAnomaly on each key in the bucket.
-void detectAndDeclareAnomalies(AnomalyTracker& tracker,
-                               const int64_t& bucketNum,
-                               const std::shared_ptr<DimToValMap>& bucket,
-                               const int64_t& eventTimestamp) {
+void detectAndDeclareAnomalies(AnomalyTracker& tracker, int64_t bucketNum,
+                               const std::shared_ptr<DimToValMap>& bucket, int64_t eventTimestamp) {
     for (const auto& kv : *bucket) {
         tracker.detectAndDeclareAnomaly(eventTimestamp, bucketNum, 0 /*metric_id*/, kv.first,
                                         kv.second);
@@ -101,9 +104,8 @@
 // timestamp (in ns) + refractoryPeriodSec.
 // If a timestamp value is negative, instead asserts that the refractory period is inapplicable
 // (either non-existant or already past).
-void checkRefractoryTimes(AnomalyTracker& tracker,
-                          const int64_t& currTimestampNs,
-                          const int32_t& refractoryPeriodSec,
+void checkRefractoryTimes(AnomalyTracker& tracker, int64_t currTimestampNs,
+                          int32_t refractoryPeriodSec,
                           const std::unordered_map<MetricDimensionKey, int64_t>& timestamps) {
     for (const auto& kv : timestamps) {
         if (kv.second < 0) {
@@ -400,6 +402,115 @@
             {{keyA, -1}, {keyB, -1}, {keyC, -1}, {keyD, -1}, {keyE, eventTimestamp6 + 7}});
 }
 
+TEST(AnomalyTrackerTest, TestProbabilityOfInforming) {
+    // Initiating StatsdStats at the start of this test, so it doesn't call rand() during the test
+    StatsdStats::getInstance();
+    srand(/*commonly used seed=*/0);
+    const int64_t bucketSizeNs = 30 * NS_PER_SEC;
+    const int32_t refractoryPeriodSec = bucketSizeNs / NS_PER_SEC;
+    int broadcastSubRandId = 1, broadcastSubAlwaysId = 2, broadcastSubNeverId = 3;
+
+    // Alert with probability of informing set to 0.5
+    Alert alertRand = createAlert("alertRand", /*metric id=*/0, /*buckets=*/1, /*triggerSum=*/0);
+    alertRand.set_refractory_period_secs(refractoryPeriodSec);
+    alertRand.set_probability_of_informing(0.5);
+    AnomalyTracker anomalyTrackerRand(alertRand, kConfigKey);
+
+    Subscription subRand = createSubscription("subRand", /*rule_type=*/Subscription::ALERT,
+                                              /*rule_id=*/alertRand.id());
+    subRand.mutable_broadcast_subscriber_details()->set_subscriber_id(broadcastSubRandId);
+    anomalyTrackerRand.addSubscription(subRand);
+
+    // Alert with probability of informing set to 1.1 (always; set by default)
+    Alert alertAlways =
+            createAlert("alertAlways", /*metric id=*/0, /*buckets=*/1, /*triggerSum=*/0);
+    alertAlways.set_refractory_period_secs(refractoryPeriodSec);
+    AnomalyTracker anomalyTrackerAlways(alertAlways, kConfigKey);
+
+    Subscription subAlways = createSubscription("subAlways", /*rule_type=*/Subscription::ALERT,
+                                                /*rule_id=*/alertAlways.id());
+    subAlways.mutable_broadcast_subscriber_details()->set_subscriber_id(broadcastSubAlwaysId);
+    anomalyTrackerAlways.addSubscription(subAlways);
+
+    // Alert with probability of informing set to -0.1 (never)
+    Alert alertNever = createAlert("alertNever", /*metric id=*/0, /*buckets=*/1, /*triggerSum=*/0);
+    alertNever.set_refractory_period_secs(refractoryPeriodSec);
+    alertNever.set_probability_of_informing(-0.1);
+    AnomalyTracker anomalyTrackerNever(alertNever, kConfigKey);
+
+    Subscription subNever = createSubscription("subNever", /*rule_type=*/Subscription::ALERT,
+                                               /*rule_id=*/alertNever.id());
+    subNever.mutable_broadcast_subscriber_details()->set_subscriber_id(broadcastSubNeverId);
+    anomalyTrackerNever.addSubscription(subNever);
+
+    // Bucket value needs to be greater than 0 to detect and declare anomaly
+    int bucketValue = 1;
+
+    int alertRandCount = 0, alertAlwaysCount = 0;
+    // The binder calls here will happen synchronously because they are in-process.
+    shared_ptr<MockPendingIntentRef> randBroadcast =
+            SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*randBroadcast,
+                sendSubscriberBroadcast(kConfigUid, kConfigId, subRand.id(), alertRand.id(), _, _))
+            .Times(3)
+            .WillRepeatedly([&alertRandCount] {
+                alertRandCount++;
+                return Status::ok();
+            });
+
+    shared_ptr<MockPendingIntentRef> alwaysBroadcast =
+            SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*alwaysBroadcast, sendSubscriberBroadcast(kConfigUid, kConfigId, subAlways.id(),
+                                                          alertAlways.id(), _, _))
+            .Times(10)
+            .WillRepeatedly([&alertAlwaysCount] {
+                alertAlwaysCount++;
+                return Status::ok();
+            });
+
+    shared_ptr<MockPendingIntentRef> neverBroadcast =
+            SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*neverBroadcast, sendSubscriberBroadcast(kConfigUid, kConfigId, subNever.id(),
+                                                         alertNever.id(), _, _))
+            .Times(0);
+
+    SubscriberReporter::getInstance().setBroadcastSubscriber(kConfigKey, broadcastSubRandId,
+                                                             randBroadcast);
+    SubscriberReporter::getInstance().setBroadcastSubscriber(kConfigKey, broadcastSubAlwaysId,
+                                                             alwaysBroadcast);
+    SubscriberReporter::getInstance().setBroadcastSubscriber(kConfigKey, broadcastSubNeverId,
+                                                             neverBroadcast);
+
+    // Trying to inform the subscription and start the refractory period countdown 10x.
+    // Deterministic sequence for anomalyTrackerRand:
+    // 0.96, 0.95, 0.95, 0.94, 0.43, 0.92, 0.92, 0.41, 0.39, 0.88
+    for (size_t i = 0; i < 10; i++) {
+        int64_t curEventTimestamp = bucketSizeNs * i;
+        anomalyTrackerRand.detectAndDeclareAnomaly(curEventTimestamp, /*bucketNum=*/i,
+                                                   /*metric_id=*/0, DEFAULT_METRIC_DIMENSION_KEY,
+                                                   bucketValue);
+        if (i <= 3) {
+            EXPECT_EQ(alertRandCount, 0);
+        } else if (i >= 4 && i <= 6) {
+            EXPECT_EQ(alertRandCount, 1);
+        } else if (i == 7) {
+            EXPECT_EQ(alertRandCount, 2);
+        } else {
+            EXPECT_EQ(alertRandCount, 3);
+        }
+        anomalyTrackerAlways.detectAndDeclareAnomaly(curEventTimestamp, /*bucketNum=*/i,
+                                                     /*metric_id=*/0, DEFAULT_METRIC_DIMENSION_KEY,
+                                                     bucketValue);
+        EXPECT_EQ(alertAlwaysCount, i + 1);
+        anomalyTrackerNever.detectAndDeclareAnomaly(curEventTimestamp, /*bucketNum=*/i,
+                                                    /*metric_id=*/0, DEFAULT_METRIC_DIMENSION_KEY,
+                                                    bucketValue);
+    }
+    SubscriberReporter::getInstance().unsetBroadcastSubscriber(kConfigKey, broadcastSubRandId);
+    SubscriberReporter::getInstance().unsetBroadcastSubscriber(kConfigKey, broadcastSubAlwaysId);
+    SubscriberReporter::getInstance().unsetBroadcastSubscriber(kConfigKey, broadcastSubNeverId);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/condition/SimpleConditionTracker_test.cpp b/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 1407cf4..b38ae4d 100644
--- a/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -146,7 +146,7 @@
     EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
 
     vector<MatchingState> matcherState;
-    vector<bool> changedCache(1, false);
+    vector<uint8_t> changedCache(1, false);
 
     // Matched stop event.
     // Check that condition is still false.
@@ -200,7 +200,7 @@
     EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]);
 
     vector<MatchingState> matcherState;
-    vector<bool> changedCache(1, false);
+    vector<uint8_t> changedCache(1, false);
 
     // Matched stop event.
     // Check that condition is changed to false.
@@ -256,7 +256,7 @@
 
     vector<sp<ConditionTracker>> allPredicates;
     vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-    vector<bool> changedCache(1, false);
+    vector<uint8_t> changedCache(1, false);
 
     conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
                                        changedCache);
@@ -343,7 +343,7 @@
     matcherState.push_back(MatchingState::kNotMatched);
     vector<sp<ConditionTracker>> allPredicates;
     vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-    vector<bool> changedCache(1, false);
+    vector<uint8_t> changedCache(1, false);
 
     conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
                                        changedCache);
@@ -419,7 +419,7 @@
         matcherState.push_back(MatchingState::kNotMatched);
         vector<sp<ConditionTracker>> allPredicates;
         vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-        vector<bool> changedCache(1, false);
+        vector<uint8_t> changedCache(1, false);
 
         conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache,
                                            changedCache);
@@ -524,7 +524,7 @@
     matcherState.push_back(MatchingState::kNotMatched);
     vector<sp<ConditionTracker>> allPredicates;
     vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-    vector<bool> changedCache(1, false);
+    vector<uint8_t> changedCache(1, false);
 
     conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache,
                                        changedCache);
@@ -616,7 +616,7 @@
         matcherState.push_back(MatchingState::kNotMatched);
         vector<sp<ConditionTracker>> allPredicates;
         vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-        vector<bool> changedCache(1, false);
+        vector<uint8_t> changedCache(1, false);
 
         conditionTracker.evaluateCondition(event1, matcherState, allPredicates, conditionCache,
                                            changedCache);
@@ -714,7 +714,7 @@
         matcherState.push_back(MatchingState::kNotMatched);
         vector<sp<ConditionTracker>> allPredicates;
         vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-        vector<bool> changedCache(1, false);
+        vector<uint8_t> changedCache(1, false);
 
         conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
                                            changedCache);
@@ -761,7 +761,7 @@
         matcherState.push_back(MatchingState::kNotMatched);
         vector<sp<ConditionTracker>> allPredicates;
         vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-        vector<bool> changedCache(1, false);
+        vector<uint8_t> changedCache(1, false);
 
         conditionTracker.evaluateCondition(event, matcherState, allPredicates, conditionCache,
                                            changedCache);
@@ -791,7 +791,7 @@
     matcherState.push_back(MatchingState::kNotMatched);
     vector<sp<ConditionTracker>> allPredicates;
     vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
-    vector<bool> changedCache(1, false);
+    vector<uint8_t> changedCache(1, false);
 
     conditionTracker.evaluateCondition(event3, matcherState, allPredicates, conditionCache,
                                        changedCache);
diff --git a/statsd/tests/e2e/Alarm_e2e_test.cpp b/statsd/tests/e2e/Alarm_e2e_test.cpp
index 93b2783..1a1329d 100644
--- a/statsd/tests/e2e/Alarm_e2e_test.cpp
+++ b/statsd/tests/e2e/Alarm_e2e_test.cpp
@@ -30,7 +30,6 @@
 
 StatsdConfig CreateStatsdConfig() {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
 
     auto alarm = config.add_alarm();
     alarm->set_id(123456);
diff --git a/statsd/tests/e2e/Anomaly_count_e2e_test.cpp b/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
index eccaafe..9719888 100644
--- a/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
+++ b/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
@@ -31,7 +31,6 @@
 
 StatsdConfig CreateStatsdConfig(int num_buckets, int threshold, int refractory_period_sec) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
 
     *config.add_atom_matcher() = wakelockAcquireMatcher;
diff --git a/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp b/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
index abc5a51..26dac91 100644
--- a/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
+++ b/statsd/tests/e2e/Anomaly_duration_sum_e2e_test.cpp
@@ -40,7 +40,6 @@
                                 DurationMetric::AggregationType aggregationType,
                                 bool nesting) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
diff --git a/statsd/tests/e2e/Attribution_e2e_test.cpp b/statsd/tests/e2e/Attribution_e2e_test.cpp
index e5f80b0..46b6539 100644
--- a/statsd/tests/e2e/Attribution_e2e_test.cpp
+++ b/statsd/tests/e2e/Attribution_e2e_test.cpp
@@ -31,7 +31,6 @@
 
 StatsdConfig CreateStatsdConfig(const Position position) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     auto attributionNodeMatcher =
         wakelockAcquireMatcher.mutable_simple_atom_matcher()->add_field_value_matcher();
diff --git a/statsd/tests/e2e/ConfigTtl_e2e_test.cpp b/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
index 0bce0ba..63c1e45 100644
--- a/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
+++ b/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
@@ -30,7 +30,6 @@
 
 StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
 
     *config.add_atom_matcher() = wakelockAcquireMatcher;
diff --git a/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp b/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp
index a098482..aa944a9 100644
--- a/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp
+++ b/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp
@@ -248,9 +248,10 @@
     ConfigKey cfgKey(0, 12345);
     sp<StatsLogProcessor> processor =
             CreateStatsLogProcessor(baseTimeNs, baseTimeNs, config, cfgKey);
-    // Uses AID_ROOT, which isn't in allowed log sources.
+    const int32_t customAppUid = AID_APP_START + 1;
+    // override default uid (which is user running the test)
     unique_ptr<LogEvent> event = CreateBatteryStateChangedEvent(
-            baseTimeNs + 2, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+            baseTimeNs + 2, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB, customAppUid);
     processor->OnLogEvent(event.get());
     ConfigMetricsReportList reports;
     vector<uint8_t> buffer;
@@ -304,7 +305,6 @@
 
 TEST_P(ConfigUpdateE2eAbTest, TestExistingGaugePullRandomOneSample) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     AtomMatcher subsystemSleepMatcher =
diff --git a/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp b/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
index ead1c46..66ca283 100644
--- a/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
+++ b/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
@@ -50,16 +50,13 @@
 sp<StatsLogProcessor> CreateStatsLogProcessor(
         const int64_t timeBaseNs, const int64_t currentTimeNs, const StatsdConfig& config,
         const ConfigKey& key, const shared_ptr<MockLogEventFilter>& logEventFilter) {
-    if (logEventFilter) {
-        // call from StatsLogProcessor constructor
-        Expectation initCall = EXPECT_CALL(*logEventFilter,
-                                           setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
-                                       .Times(1);
-        EXPECT_CALL(*logEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
-                .Times(1)
-                .After(initCall);
-    }
-
+    // call from StatsLogProcessor constructor
+    Expectation initCall =
+            EXPECT_CALL(*logEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+                    .Times(1);
+    EXPECT_CALL(*logEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
+            .Times(1)
+            .After(initCall);
     return CreateStatsLogProcessor(timeBaseNs, currentTimeNs, config, key, nullptr, 0, new UidMap(),
                                    logEventFilter);
 }
@@ -67,26 +64,17 @@
 }  // Anonymous namespace.
 
 // Setup for test fixture.
-class ConfigUpdateE2eTest : public testing::TestWithParam<bool> {
+class ConfigUpdateE2eTest : public testing::Test {
 protected:
     std::shared_ptr<MockLogEventFilter> mLogEventFilter;
 
     void SetUp() override {
-        mLogEventFilter = GetParam() ? std::make_shared<MockLogEventFilter>() : nullptr;
-    }
-
-public:
-    static std::string ToString(testing::TestParamInfo<bool> info) {
-        return info.param ? "WithLogEventFilter" : "NoLogEventFilter";
+        mLogEventFilter = std::make_shared<MockLogEventFilter>();
     }
 };
 
-INSTANTIATE_TEST_SUITE_P(ConfigUpdateE2eTest, ConfigUpdateE2eTest, testing::Bool(),
-                         ConfigUpdateE2eTest::ToString);
-
-TEST_P(ConfigUpdateE2eTest, TestEventMetric) {
+TEST_F(ConfigUpdateE2eTest, TestEventMetric) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
 
     AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher();
     *config.add_atom_matcher() = syncStartMatcher;
@@ -172,7 +160,6 @@
 
     // Do update. Add matchers/conditions in different order to force indices to change.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
 
     *newConfig.add_atom_matcher() = screenOnMatcher;
     *newConfig.add_atom_matcher() = batteryPluggedUsbMatcher;
@@ -193,11 +180,8 @@
     *newConfig.add_event_metric() = eventPersist;
 
     int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter,
-                    setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
-                .Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+            .Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Send events after the update.
@@ -311,9 +295,8 @@
     EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync3");
 }
 
-TEST_P(ConfigUpdateE2eTest, TestCountMetric) {
+TEST_F(ConfigUpdateE2eTest, TestCountMetric) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
 
     AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher();
     *config.add_atom_matcher() = syncStartMatcher;
@@ -416,7 +399,6 @@
 
     // Do update. Add matchers/conditions in different order to force indices to change.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
 
     *newConfig.add_atom_matcher() = screenOnMatcher;
     *newConfig.add_atom_matcher() = screenOffMatcher;
@@ -436,11 +418,8 @@
     *newConfig.add_count_metric() = countPersist;
 
     int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter,
-                    setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
-                .Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+            .Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Send events after the update. Counts reset to 0 since this is a new bucket.
@@ -569,9 +548,8 @@
     ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 2);
 }
 
-TEST_P(ConfigUpdateE2eTest, TestDurationMetric) {
+TEST_F(ConfigUpdateE2eTest, TestDurationMetric) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
 
     AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher();
     *config.add_atom_matcher() = syncStartMatcher;
@@ -701,7 +679,6 @@
 
     // Do update. Add matchers/conditions in different order to force indices to change.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
 
     *newConfig.add_atom_matcher() = wakelockReleaseMatcher;
     *newConfig.add_atom_matcher() = syncStopMatcher;
@@ -725,11 +702,8 @@
     // At update, only uid 1 is syncing & holding a wakelock, duration=33. Max is paused for uid3.
     // Before the update, only uid2 will report a duration for max, since others are started/paused.
     int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter,
-                    setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
-                .Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+            .Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Send events after the update.
@@ -927,9 +901,8 @@
     ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 20 * NS_PER_SEC);
 }
 
-TEST_P(ConfigUpdateE2eTest, TestGaugeMetric) {
+TEST_F(ConfigUpdateE2eTest, TestGaugeMetric) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     AtomMatcher appStartMatcher = CreateSimpleAtomMatcher("AppStart", util::APP_START_OCCURRED);
@@ -988,15 +961,13 @@
     ConfigKey key(123, 987);
     uint64_t bucketStartTimeNs = getElapsedRealtimeNs();  // 0:10
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
-    if (GetParam()) {
-        // call from StatsLogProcessor constructor
-        Expectation initCall = EXPECT_CALL(*mLogEventFilter,
-                                           setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
-                                       .Times(1);
-        EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
-                .Times(1)
-                .After(initCall);
-    }
+    // call from StatsLogProcessor constructor
+    Expectation initCall =
+            EXPECT_CALL(*mLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+                    .Times(1);
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
+            .Times(1)
+            .After(initCall);
     sp<StatsLogProcessor> processor =
             CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key,
                                     SharedRefBase::make<FakeSubsystemSleepCallback>(),
@@ -1041,7 +1012,6 @@
 
     // Do the update. Add matchers/conditions in different order to force indices to change.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
     newConfig.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     *newConfig.add_atom_matcher() = screenOffMatcher;
@@ -1066,9 +1036,7 @@
 
     int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
     // Update pulls gaugePullPersist and gaugeNew.
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Verify puller manager is properly set.
@@ -1335,9 +1303,8 @@
     EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 902);
 }
 
-TEST_P(ConfigUpdateE2eTest, TestValueMetric) {
+TEST_F(ConfigUpdateE2eTest, TestValueMetric) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     AtomMatcher brightnessMatcher = CreateScreenBrightnessChangedAtomMatcher();
@@ -1389,15 +1356,13 @@
     ConfigKey key(123, 987);
     uint64_t bucketStartTimeNs = getElapsedRealtimeNs();
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
-    if (GetParam()) {
-        // call from StatsLogProcessor constructor
-        Expectation initCall = EXPECT_CALL(*mLogEventFilter,
-                                           setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
-                                       .Times(1);
-        EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
-                .Times(1)
-                .After(initCall);
-    }
+    // call from StatsLogProcessor constructor
+    Expectation initCall =
+            EXPECT_CALL(*mLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+                    .Times(1);
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
+            .Times(1)
+            .After(initCall);
     // Config creation triggers pull #1.
     sp<StatsLogProcessor> processor =
             CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key,
@@ -1425,7 +1390,6 @@
 
     // Do the update. Add matchers/conditions in different order to force indices to change.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
     newConfig.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     *newConfig.add_atom_matcher() = screenOffMatcher;
@@ -1450,9 +1414,7 @@
 
     int64_t updateTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC;
     // Update pulls valuePullPersist and valueNew. Pull #3.
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Verify puller manager is properly set.
@@ -1675,9 +1637,8 @@
     EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::DUMP_REPORT_REQUESTED);
 }
 
-TEST_P(ConfigUpdateE2eTest, TestKllMetric) {
+TEST_F(ConfigUpdateE2eTest, TestKllMetric) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
 
     AtomMatcher brightnessMatcher = CreateScreenBrightnessChangedAtomMatcher();
     *config.add_atom_matcher() = brightnessMatcher;
@@ -1731,7 +1692,6 @@
 
     // Do the update. Add matchers/conditions in different order to force indices to change.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
 
     *newConfig.add_atom_matcher() = screenOffMatcher;
     *newConfig.add_atom_matcher() = unpluggedMatcher;
@@ -1757,11 +1717,8 @@
     *newConfig.add_kll_metric() = kllPersist;
 
     int64_t updateTimeNs = bucketStartTimeNs + 30 * NS_PER_SEC;
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter,
-                    setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
-                .Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+            .Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Send events after the update. This is a new bucket.
@@ -1893,10 +1850,9 @@
     EXPECT_EQ(kllPersistAfter.kll_metrics().skipped_size(), 0);
 }
 
-TEST_P(ConfigUpdateE2eTest, TestMetricActivation) {
+TEST_F(ConfigUpdateE2eTest, TestMetricActivation) {
     ALOGE("Start ConfigUpdateE2eTest#TestMetricActivation");
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
 
     string immediateTag = "immediate", bootTag = "boot", childTag = "child";
 
@@ -2000,7 +1956,6 @@
 
     // Do update. Add matchers/conditions in different order to force indices to change.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
     newConfig.set_hash_strings_in_metric_report(false);  // Modify metadata for fun.
 
     // Change combination matcher, will mean combination metric isn't active after update.
@@ -2021,11 +1976,8 @@
     *newConfig.add_metric_activation() = immediateMetricActivation;
 
     int64_t updateTimeNs = bucketStartTimeNs + 40 * NS_PER_SEC;
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter,
-                    setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
-                .Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+            .Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // The reboot will write to disk again, so sleep for 1 second to avoid this.
@@ -2149,9 +2101,8 @@
     ALOGE("End ConfigUpdateE2eTest#TestMetricActivation");
 }
 
-TEST_P(ConfigUpdateE2eTest, TestAnomalyCountMetric) {
+TEST_F(ConfigUpdateE2eTest, TestAnomalyCountMetric) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
 
     AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher();
     *config.add_atom_matcher() = syncStartMatcher;
@@ -2285,7 +2236,6 @@
 
     // Do config update.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
     *newConfig.add_atom_matcher() = wakelockAcquireMatcher;
     *newConfig.add_atom_matcher() = syncStartMatcher;
 
@@ -2325,11 +2275,8 @@
     SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast);
 
     int64_t updateTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC;
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter,
-                    setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
-                .Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), processor.get()))
+            .Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Within refractory of AlertPreserve, but AlertNew should fire since the full bucket has 2.
@@ -2365,9 +2312,8 @@
     SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
 }
 
-TEST_P(ConfigUpdateE2eTest, TestAnomalyDurationMetric) {
+TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
 
     AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = wakelockAcquireMatcher;
@@ -2469,17 +2415,15 @@
     SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast);
 
     const sp<UidMap> uidMap = new UidMap();
-    if (GetParam()) {
-        // call from StatsLogProcessor constructor
-        Expectation initCall = EXPECT_CALL(*mLogEventFilter,
-                                           setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
-                                       .Times(1);
-        EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
-                .Times(1)
-                .After(initCall);
-    }
-    const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
-            uidMap, /* queue */ nullptr, /* LogEventFilter */ mLogEventFilter);
+    // call from StatsLogProcessor constructor
+    Expectation initCall =
+            EXPECT_CALL(*mLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+                    .Times(1);
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), _))
+            .Times(1)
+            .After(initCall);
+    const shared_ptr<StatsService> service =
+            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr, mLogEventFilter);
     sp<StatsLogProcessor> processor = service->mProcessor;
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
     int64_t bucketStartTimeNs = processor->mTimeBaseNs;
@@ -2623,7 +2567,6 @@
 
     // Do config update.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
     *newConfig.add_atom_matcher() = wakelockAcquireMatcher;
     *newConfig.add_atom_matcher() = screenOffMatcher;
     *newConfig.add_atom_matcher() = wakelockReleaseMatcher;
@@ -2666,9 +2609,7 @@
     SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast);
 
     int64_t updateTimeNs = bucket2StartTimeNs + 50 * NS_PER_SEC;
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Alert preserve will set alarm after the refractory period, but alert new will set it for
@@ -2728,9 +2669,8 @@
     SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
 }
 
-TEST_P(ConfigUpdateE2eTest, TestAlarms) {
+TEST_F(ConfigUpdateE2eTest, TestAlarms) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     Alarm alarmPreserve = createAlarm("AlarmPreserve", /*offset*/ 5 * MS_PER_SEC,
                                       /*period*/ TimeUnitToBucketSizeInMillis(ONE_MINUTE));
     Alarm alarmReplace = createAlarm("AlarmReplace", /*offset*/ 1,
@@ -2826,7 +2766,6 @@
 
     // Do config update.
     StatsdConfig newConfig;
-    newConfig.add_allowed_log_source("AID_ROOT");
 
     // Change alarm replace's definition.
     alarmReplace.set_period_millis(TimeUnitToBucketSizeInMillis(ONE_MINUTE));
@@ -2856,9 +2795,7 @@
                 return Status::ok();
             });
     SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast);
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
     processor->OnConfigUpdated((startTimeSec + 90) * NS_PER_SEC, key, newConfig);
     // After the update, the alarm time should remain unchanged since alarm replace now fires every
     // minute with no offset.
@@ -2898,9 +2835,8 @@
     SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
 }
 
-TEST_P(ConfigUpdateE2eTest, TestNewDurationExistingWhat) {
+TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhat) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
 
@@ -2930,11 +2866,8 @@
     durationMetric->set_bucket(FIVE_MINUTES);
 
     uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;  // 1:00
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter,
-                    setAtomIds(CreateAtomIdSetFromConfig(config), processor.get()))
-                .Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), processor.get()))
+            .Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, config);
 
     event = CreateReleaseWakelockEvent(bucketStartTimeNs + 80 * NS_PER_SEC, attributionUids1,
@@ -2965,9 +2898,8 @@
     EXPECT_EQ(bucketInfo.duration_nanos(), 20 * NS_PER_SEC);
 }
 
-TEST_P(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedCondition) {
+TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedCondition) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
@@ -3025,11 +2957,8 @@
             CreateDimensions(util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /*uid*/});
 
     uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;  // 1:00
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter,
-                    setAtomIds(CreateAtomIdSetFromConfig(config), processor.get()))
-                .Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), processor.get()))
+            .Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, config);
 
     event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 73 * NS_PER_SEC, app2Uid);  // 1:13
@@ -3069,9 +2998,8 @@
     EXPECT_EQ(bucketInfo.duration_nanos(), 17 * NS_PER_SEC);
 }
 
-TEST_P(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedState) {
+TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedState) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
 
@@ -3144,11 +3072,8 @@
             CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/});
 
     uint64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;  // 1:00
-    if (GetParam()) {
-        EXPECT_CALL(*mLogEventFilter,
-                    setAtomIds(CreateAtomIdSetFromConfig(config), processor.get()))
-                .Times(1);
-    }
+    EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), processor.get()))
+            .Times(1);
     processor->OnConfigUpdated(updateTimeNs, key, config);
 
     event = CreateAcquireWakelockEvent(bucketStartTimeNs + 72 * NS_PER_SEC, attributionUids2,
diff --git a/statsd/tests/e2e/CountMetric_e2e_test.cpp b/statsd/tests/e2e/CountMetric_e2e_test.cpp
index 10fdb8e..f7e5aea 100644
--- a/statsd/tests/e2e/CountMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -39,7 +39,6 @@
 TEST(CountMetricE2eTest, TestInitialConditionChanges) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");     // LogEvent defaults to UID of root.
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     auto syncStartMatcher = CreateSyncStartAtomMatcher();
@@ -116,7 +115,6 @@
 TEST(CountMetricE2eTest, TestConditionTrueNanos) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");     // LogEvent defaults to UID of root.
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     AtomMatcher syncStartMatcher = CreateSyncStartAtomMatcher();
@@ -253,6 +251,7 @@
 
     ASSERT_EQ(1, reports.reports_size());
     ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_estimated_data_bytes());
     EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
     StatsLogReport::CountMetricDataWrapper countMetrics;
     sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
@@ -283,7 +282,6 @@
 TEST(CountMetricE2eTest, TestSlicedState) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto syncStartMatcher = CreateSyncStartAtomMatcher();
     *config.add_atom_matcher() = syncStartMatcher;
@@ -432,7 +430,6 @@
 TEST(CountMetricE2eTest, TestSlicedStateWithMap) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto syncStartMatcher = CreateSyncStartAtomMatcher();
     *config.add_atom_matcher() = syncStartMatcher;
@@ -611,7 +608,6 @@
 TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto appCrashMatcher =
             CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
@@ -815,7 +811,6 @@
 TEST(CountMetricE2eTest, TestMultipleSlicedStates) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto appCrashMatcher =
             CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
@@ -1090,7 +1085,6 @@
 TEST(CountMetricE2eTest, TestUploadThreshold) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto appCrashMatcher = CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
     *config.add_atom_matcher() = appCrashMatcher;
@@ -1161,7 +1155,6 @@
 
 TEST(CountMetricE2eTest, TestRepeatedFieldsAndEmptyArrays) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedAtomMatcher =
             CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
@@ -1229,7 +1222,6 @@
 
 TEST(CountMetricE2eTest, TestMatchRepeatedFieldPositionAny) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedStateAnyOnAtomMatcher =
             CreateTestAtomRepeatedStateAnyOnAtomMatcher();
@@ -1295,7 +1287,6 @@
 
 TEST(CountMetricE2eTest, TestRepeatedFieldDimension_PositionFirst) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedAtomMatcher =
             CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
@@ -1383,7 +1374,6 @@
 
 TEST(CountMetricE2eTest, TestRepeatedFieldDimension_PositionLast) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedAtomMatcher =
             CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
@@ -1462,7 +1452,6 @@
 
 TEST(CountMetricE2eTest, TestRepeatedFieldDimension_PositionAll) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedAtomMatcher =
             CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
@@ -1568,7 +1557,6 @@
 
 TEST(CountMetricE2eTest, TestMultipleRepeatedFieldDimensions_PositionFirst) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedAtomMatcher =
             CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
@@ -1683,7 +1671,6 @@
 
 TEST(CountMetricE2eTest, TestMultipleRepeatedFieldDimensions_PositionAll) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedAtomMatcher =
             CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
@@ -1813,7 +1800,6 @@
 
 TEST(CountMetricE2eTest, TestConditionSlicedByRepeatedUidWithUidDimension) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher uidProcessStateChangedAtomMatcher = CreateUidProcessStateChangedAtomMatcher();
     AtomMatcher repeatedStateFirstOffAtomMatcher = CreateTestAtomRepeatedStateFirstOffAtomMatcher();
@@ -1946,7 +1932,6 @@
 
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher appCrashMatcher =
             CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
@@ -2029,4 +2014,4 @@
 }  // namespace android
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
\ No newline at end of file
+#endif
diff --git a/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index be04f7f..308ef52 100644
--- a/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -29,7 +29,6 @@
 
 TEST(DurationMetricE2eTest, TestOneBucket) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
@@ -95,6 +94,7 @@
     backfillStartEndTimestamp(&reports);
     ASSERT_EQ(1, reports.reports_size());
     ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_estimated_data_bytes());
     EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id());
     EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
 
@@ -112,7 +112,6 @@
 
 TEST(DurationMetricE2eTest, TestTwoBuckets) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
@@ -198,7 +197,6 @@
 
 TEST(DurationMetricE2eTest, TestWithActivation) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
@@ -254,7 +252,7 @@
                                               activeConfigs.end());
                 return true;
             },
-            mockLogEventFilter);
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), &processor))
             .Times(1);
@@ -377,7 +375,6 @@
 
 TEST(DurationMetricE2eTest, TestWithCondition) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
@@ -470,8 +467,6 @@
 
 TEST(DurationMetricE2eTest, TestWithSlicedCondition) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
-    auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
@@ -574,7 +569,6 @@
 
 TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
@@ -741,7 +735,6 @@
 TEST(DurationMetricE2eTest, TestWithSlicedState) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
     *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
@@ -884,7 +877,6 @@
 TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
     *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
@@ -1046,7 +1038,6 @@
 TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
     *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
@@ -1194,7 +1185,6 @@
 TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
@@ -1238,7 +1228,6 @@
 TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) {
     // Initialize config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
@@ -1485,7 +1474,6 @@
 
 TEST(DurationMetricE2eTest, TestUploadThreshold) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
     auto screenOffMatcher = CreateScreenTurnedOffAtomMatcher();
@@ -1580,7 +1568,6 @@
 
 TEST(DurationMetricE2eTest, TestConditionOnRepeatedEnumField) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher repeatedStateFirstOffAtomMatcher = CreateTestAtomRepeatedStateFirstOffAtomMatcher();
     AtomMatcher repeatedStateFirstOnAtomMatcher = CreateTestAtomRepeatedStateFirstOnAtomMatcher();
@@ -1669,7 +1656,6 @@
     ShardOffsetProvider::getInstance().setShardOffset(5);
 
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     *config.add_atom_matcher() = CreateStartScheduledJobAtomMatcher();
     *config.add_atom_matcher() = CreateFinishScheduledJobAtomMatcher();
diff --git a/statsd/tests/e2e/EventMetric_e2e_test.cpp b/statsd/tests/e2e/EventMetric_e2e_test.cpp
index 81a6d91..6909dc2 100644
--- a/statsd/tests/e2e/EventMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/EventMetric_e2e_test.cpp
@@ -41,7 +41,6 @@
 
 TEST_F(EventMetricE2eTest, TestEventMetricDataAggregated) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = wakelockAcquireMatcher;
@@ -85,8 +84,10 @@
     ASSERT_EQ(reports.reports_size(), 1);
 
     ConfigMetricsReport report = reports.reports(0);
+    EXPECT_TRUE(report.has_estimated_data_bytes());
     ASSERT_EQ(report.metrics_size(), 1);
     StatsLogReport wakelockEventMetricReport = report.metrics(0);
+    EXPECT_TRUE(wakelockEventMetricReport.has_estimated_data_bytes());
     EXPECT_EQ(wakelockEventMetricReport.metric_id(), wakelockEventMetric.id());
     EXPECT_TRUE(wakelockEventMetricReport.has_event_metrics());
     ASSERT_EQ(wakelockEventMetricReport.event_metrics().data_size(), 3);
@@ -103,7 +104,6 @@
 
 TEST_F(EventMetricE2eTest, TestRepeatedFieldsAndEmptyArrays) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedAtomMatcher =
             CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
@@ -129,7 +129,7 @@
     bool boolArray[boolArrayLength];
     boolArray[0] = 1;
     boolArray[1] = 0;
-    vector<bool> boolArrayVector = {1, 0};
+    vector<uint8_t> boolArrayVector = {1, 0};
     vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF};
 
     events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
@@ -195,7 +195,6 @@
 
 TEST_F(EventMetricE2eTest, TestMatchRepeatedFieldPositionFirst) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedStateFirstOnAtomMatcher =
             CreateTestAtomRepeatedStateFirstOnAtomMatcher();
@@ -257,6 +256,90 @@
     EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArrayMatch));
 }
 
+TEST_F(EventMetricE2eTest, TestDumpReportIncrementsReportNumber) {
+    StatsdConfig config;
+
+    AtomMatcher testAtomReportedStateFirstOnAtomMatcher =
+            CreateTestAtomRepeatedStateFirstOnAtomMatcher();
+    *config.add_atom_matcher() = testAtomReportedStateFirstOnAtomMatcher;
+
+    EventMetric testAtomReportedEventMetric = createEventMetric(
+            "EventTestAtomReported", testAtomReportedStateFirstOnAtomMatcher.id(), nullopt);
+    *config.add_event_metric() = testAtomReportedEventMetric;
+
+    ConfigKey key(123, 987);
+    uint64_t configUpdateTime = 10000000000;  // 0:10
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(configUpdateTime, configUpdateTime, config, key);
+
+    uint64_t dumpTimeNs = configUpdateTime + 100 * NS_PER_SEC;
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(reports.reports_size(), 1);
+
+    EXPECT_EQ(reports.report_number(), 1);
+    EXPECT_EQ(reports.statsd_stats_id(), StatsdStats::getInstance().getStatsdStatsId());
+
+    buffer.clear();
+    processor->onDumpReport(key, dumpTimeNs + 100, true, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(reports.reports_size(), 1);
+
+    EXPECT_EQ(reports.report_number(), 2);
+    EXPECT_EQ(reports.statsd_stats_id(), StatsdStats::getInstance().getStatsdStatsId());
+}
+
+TEST_F(EventMetricE2eTest, TestEventMetricSampling) {
+    // Set srand seed to make rand deterministic for testing.
+    srand(0);
+
+    StatsdConfig config;
+
+    AtomMatcher batterySaverOnMatcher = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = batterySaverOnMatcher;
+
+    EventMetric batterySaverOnEventMetric =
+            createEventMetric("EventBatterySaverOn", batterySaverOnMatcher.id(), nullopt);
+    batterySaverOnEventMetric.set_sampling_percentage(50);
+    *config.add_event_metric() = batterySaverOnEventMetric;
+
+    ConfigKey key(123, 987);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+
+    // Initialize log events before update.
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    for (int i = 0; i < 100; i++) {
+        events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + (10 + 10 * i) * NS_PER_SEC));
+    }
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    uint64_t dumpTimeNs = bucketStartTimeNs + 2000 * NS_PER_SEC;
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+    ASSERT_EQ(reports.reports_size(), 1);
+
+    ConfigMetricsReport report = reports.reports(0);
+    ASSERT_EQ(report.metrics_size(), 1);
+    StatsLogReport metricReport = report.metrics(0);
+    EXPECT_EQ(metricReport.metric_id(), batterySaverOnEventMetric.id());
+    EXPECT_TRUE(metricReport.has_event_metrics());
+    ASSERT_EQ(metricReport.event_metrics().data_size(), 46);
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
index 474cb7a..e9d5b0e 100644
--- a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
+++ b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
@@ -37,7 +37,6 @@
 StatsdConfig CreateStatsdConfig(const GaugeMetric::SamplingType sampling_type,
                                 bool useCondition = true) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
     auto atomMatcher = CreateSimpleAtomMatcher("TestMatcher", ATOM_TAG);
     *config.add_atom_matcher() = atomMatcher;
@@ -146,6 +145,7 @@
     backfillAggregatedAtoms(&reports);
     ASSERT_EQ(1, reports.reports_size());
     ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_estimated_data_bytes());
     StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
     sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
     ASSERT_GT((int)gaugeMetrics.data_size(), 1);
@@ -956,6 +956,420 @@
     EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0);
 }
 
+TEST(GaugeMetricE2ePulledTest, TestGaugeMetricPullProbabilityWithTriggerEvent) {
+    // Initiating StatsdStats at the start of this test, so it doesn't call rand() during the test.
+    StatsdStats::getInstance();
+    // Set srand seed to make rand deterministic for testing.
+    srand(0);
+
+    auto config = CreateStatsdConfig(GaugeMetric::FIRST_N_SAMPLES, /*useCondition=*/false);
+    auto gaugeMetric = config.mutable_gauge_metric(0);
+    gaugeMetric->set_pull_probability(50);
+    auto triggerEventMatcher = CreateScreenTurnedOnAtomMatcher();
+    gaugeMetric->set_trigger_event(triggerEventMatcher.id());
+    gaugeMetric->set_max_num_gauge_atoms_per_bucket(200);
+    gaugeMetric->set_bucket(ONE_HOUR);
+
+    int64_t configAddedTimeNs = 60 * NS_PER_SEC;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor =
+            CreateStatsLogProcessor(configAddedTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG);
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    // First bucket events.
+    for (int i = 0; i < 30; i++) {
+        events.push_back(CreateScreenStateChangedEvent(configAddedTimeNs + (i * 10 * NS_PER_SEC),
+                                                       android::view::DISPLAY_STATE_ON));
+    }
+    // Second bucket events.
+    for (int i = 0; i < 30; i++) {
+        events.push_back(CreateScreenStateChangedEvent(
+                configAddedTimeNs + bucketSizeNs + (i * 10 * NS_PER_SEC),
+                android::view::DISPLAY_STATE_ON));
+    }
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 7 * bucketSizeNs + 10, false, true,
+                            ADB_DUMP, FAST, &buffer);
+
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ((int)gaugeMetrics.data_size(), 2);  // 2 sets of data for each pull.
+
+    // Data 1
+    auto data = gaugeMetrics.data(0);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_1",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(2, data.bucket_info_size());
+
+    // Data 1, Bucket 1
+    ASSERT_EQ(13, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+            {(int64_t)60 * NS_PER_SEC, (int64_t)80 * NS_PER_SEC, (int64_t)90 * NS_PER_SEC,
+             (int64_t)130 * NS_PER_SEC, (int64_t)150 * NS_PER_SEC, (int64_t)170 * NS_PER_SEC,
+             (int64_t)190 * NS_PER_SEC, (int64_t)200 * NS_PER_SEC, (int64_t)240 * NS_PER_SEC,
+             (int64_t)250 * NS_PER_SEC, (int64_t)300 * NS_PER_SEC, (int64_t)330 * NS_PER_SEC,
+             (int64_t)340 * NS_PER_SEC});
+
+    // Data 1, Bucket 2
+    ASSERT_EQ(18, data.bucket_info(1).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(1), configAddedTimeNs + bucketSizeNs,
+            configAddedTimeNs + 2 * bucketSizeNs,
+            {(int64_t)3660 * NS_PER_SEC, (int64_t)3680 * NS_PER_SEC, (int64_t)3700 * NS_PER_SEC,
+             (int64_t)3710 * NS_PER_SEC, (int64_t)3720 * NS_PER_SEC, (int64_t)3740 * NS_PER_SEC,
+             (int64_t)3780 * NS_PER_SEC, (int64_t)3790 * NS_PER_SEC, (int64_t)3820 * NS_PER_SEC,
+             (int64_t)3850 * NS_PER_SEC, (int64_t)3860 * NS_PER_SEC, (int64_t)3870 * NS_PER_SEC,
+             (int64_t)3880 * NS_PER_SEC, (int64_t)3900 * NS_PER_SEC, (int64_t)3910 * NS_PER_SEC,
+             (int64_t)3920 * NS_PER_SEC, (int64_t)3930 * NS_PER_SEC, (int64_t)3940 * NS_PER_SEC});
+
+    // Data 2
+    data = gaugeMetrics.data(1);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_2",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(2, data.bucket_info_size());
+
+    // Data 2, Bucket 1
+    ASSERT_EQ(13, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+            {(int64_t)60 * NS_PER_SEC, (int64_t)80 * NS_PER_SEC, (int64_t)90 * NS_PER_SEC,
+             (int64_t)130 * NS_PER_SEC, (int64_t)150 * NS_PER_SEC, (int64_t)170 * NS_PER_SEC,
+             (int64_t)190 * NS_PER_SEC, (int64_t)200 * NS_PER_SEC, (int64_t)240 * NS_PER_SEC,
+             (int64_t)250 * NS_PER_SEC, (int64_t)300 * NS_PER_SEC, (int64_t)330 * NS_PER_SEC,
+             (int64_t)340 * NS_PER_SEC});
+
+    // Data 2, Bucket 2
+    ASSERT_EQ(18, data.bucket_info(1).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(1), configAddedTimeNs + bucketSizeNs,
+            configAddedTimeNs + 2 * bucketSizeNs,
+            {(int64_t)3660 * NS_PER_SEC, (int64_t)3680 * NS_PER_SEC, (int64_t)3700 * NS_PER_SEC,
+             (int64_t)3710 * NS_PER_SEC, (int64_t)3720 * NS_PER_SEC, (int64_t)3740 * NS_PER_SEC,
+             (int64_t)3780 * NS_PER_SEC, (int64_t)3790 * NS_PER_SEC, (int64_t)3820 * NS_PER_SEC,
+             (int64_t)3850 * NS_PER_SEC, (int64_t)3860 * NS_PER_SEC, (int64_t)3870 * NS_PER_SEC,
+             (int64_t)3880 * NS_PER_SEC, (int64_t)3900 * NS_PER_SEC, (int64_t)3910 * NS_PER_SEC,
+             (int64_t)3920 * NS_PER_SEC, (int64_t)3930 * NS_PER_SEC, (int64_t)3940 * NS_PER_SEC});
+}
+
+TEST(GaugeMetricE2ePulledTest, TestGaugeMetricPullProbabilityWithBucketBoundaryAlarm) {
+    // Initiating StatsdStats at the start of this test, so it doesn't call rand() during the test.
+    StatsdStats::getInstance();
+    // Set srand seed to make rand deterministic for testing.
+    srand(0);
+
+    auto config = CreateStatsdConfig(GaugeMetric::FIRST_N_SAMPLES, /*useCondition=*/false);
+    auto gaugeMetric = config.mutable_gauge_metric(0);
+    gaugeMetric->set_pull_probability(50);
+    gaugeMetric->set_max_num_gauge_atoms_per_bucket(200);
+
+    int64_t baseTimeNs = 5 * 60 * NS_PER_SEC;
+    int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor =
+            CreateStatsLogProcessor(configAddedTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG);
+
+    // Pulling alarm arrives on time and resets the sequential pulling alarm.
+    for (int i = 1; i < 31; i++) {
+        processor->informPullAlarmFired(configAddedTimeNs + i * bucketSizeNs);
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 32 * bucketSizeNs + 10, false, true,
+                            ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ((int)gaugeMetrics.data_size(), 2);
+
+    // Data 1
+    auto data = gaugeMetrics.data(0);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_1",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(14, data.bucket_info_size());
+
+    EXPECT_EQ(1, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs, {configAddedTimeNs});
+
+    EXPECT_EQ(1, data.bucket_info(1).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(1), configAddedTimeNs + 2 * bucketSizeNs,
+                             configAddedTimeNs + 3 * bucketSizeNs,
+                             {configAddedTimeNs + 2 * bucketSizeNs});  // 1200000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(2).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(2), configAddedTimeNs + 3 * bucketSizeNs,
+                             configAddedTimeNs + 4 * bucketSizeNs,
+                             {(int64_t)configAddedTimeNs + 3 * bucketSizeNs});  // 1500000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(3).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(3), configAddedTimeNs + 7 * bucketSizeNs,
+                             configAddedTimeNs + 8 * bucketSizeNs,
+                             {configAddedTimeNs + 7 * bucketSizeNs});  // 2700000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(4).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(4), configAddedTimeNs + 9 * bucketSizeNs,
+                             configAddedTimeNs + 10 * bucketSizeNs,
+                             {configAddedTimeNs + 9 * bucketSizeNs});  // 3300000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(5).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(5), configAddedTimeNs + 11 * bucketSizeNs,
+                             configAddedTimeNs + 12 * bucketSizeNs,
+                             {configAddedTimeNs + 11 * bucketSizeNs});  // 3900000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(6).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(6), configAddedTimeNs + 13 * bucketSizeNs,
+                             configAddedTimeNs + 14 * bucketSizeNs,
+                             {configAddedTimeNs + 13 * bucketSizeNs});  // 4500000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(7).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(7), configAddedTimeNs + 14 * bucketSizeNs,
+                             configAddedTimeNs + 15 * bucketSizeNs,
+                             {configAddedTimeNs + 14 * bucketSizeNs});  // 4800000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(8).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(8), configAddedTimeNs + 18 * bucketSizeNs,
+                             configAddedTimeNs + 19 * bucketSizeNs,
+                             {configAddedTimeNs + 18 * bucketSizeNs});  // 6000000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(9).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(9), configAddedTimeNs + 19 * bucketSizeNs,
+                             configAddedTimeNs + 20 * bucketSizeNs,
+                             {configAddedTimeNs + 19 * bucketSizeNs});  // 6300000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(10).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(10), configAddedTimeNs + 24 * bucketSizeNs,
+                             configAddedTimeNs + 25 * bucketSizeNs,
+                             {configAddedTimeNs + 24 * bucketSizeNs});  // 7800000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(11).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(11), configAddedTimeNs + 27 * bucketSizeNs,
+                             configAddedTimeNs + 28 * bucketSizeNs,
+                             {configAddedTimeNs + 27 * bucketSizeNs});  // 8700000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(12).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(12), configAddedTimeNs + 28 * bucketSizeNs,
+                             configAddedTimeNs + 29 * bucketSizeNs,
+                             {configAddedTimeNs + 28 * bucketSizeNs});  // 9000000000000ns
+
+    EXPECT_EQ(1, data.bucket_info(13).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(13), configAddedTimeNs + 30 * bucketSizeNs,
+                             configAddedTimeNs + 31 * bucketSizeNs,
+                             {configAddedTimeNs + 30 * bucketSizeNs});  // 9600000000000ns
+
+    // Data 2
+    data = gaugeMetrics.data(1);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_2",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(14, data.bucket_info_size());
+
+    EXPECT_EQ(1, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs, {configAddedTimeNs});
+
+    EXPECT_EQ(1, data.bucket_info(1).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(1), configAddedTimeNs + 2 * bucketSizeNs,
+                             configAddedTimeNs + 3 * bucketSizeNs,
+                             {configAddedTimeNs + 2 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(2).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(2), configAddedTimeNs + 3 * bucketSizeNs,
+                             configAddedTimeNs + 4 * bucketSizeNs,
+                             {(int64_t)configAddedTimeNs + 3 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(3).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(3), configAddedTimeNs + 7 * bucketSizeNs,
+                             configAddedTimeNs + 8 * bucketSizeNs,
+                             {configAddedTimeNs + 7 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(4).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(4), configAddedTimeNs + 9 * bucketSizeNs,
+                             configAddedTimeNs + 10 * bucketSizeNs,
+                             {configAddedTimeNs + 9 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(5).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(5), configAddedTimeNs + 11 * bucketSizeNs,
+                             configAddedTimeNs + 12 * bucketSizeNs,
+                             {configAddedTimeNs + 11 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(6).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(6), configAddedTimeNs + 13 * bucketSizeNs,
+                             configAddedTimeNs + 14 * bucketSizeNs,
+                             {configAddedTimeNs + 13 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(7).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(7), configAddedTimeNs + 14 * bucketSizeNs,
+                             configAddedTimeNs + 15 * bucketSizeNs,
+                             {configAddedTimeNs + 14 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(8).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(8), configAddedTimeNs + 18 * bucketSizeNs,
+                             configAddedTimeNs + 19 * bucketSizeNs,
+                             {configAddedTimeNs + 18 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(9).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(9), configAddedTimeNs + 19 * bucketSizeNs,
+                             configAddedTimeNs + 20 * bucketSizeNs,
+                             {configAddedTimeNs + 19 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(10).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(10), configAddedTimeNs + 24 * bucketSizeNs,
+                             configAddedTimeNs + 25 * bucketSizeNs,
+                             {configAddedTimeNs + 24 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(11).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(11), configAddedTimeNs + 27 * bucketSizeNs,
+                             configAddedTimeNs + 28 * bucketSizeNs,
+                             {configAddedTimeNs + 27 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(12).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(12), configAddedTimeNs + 28 * bucketSizeNs,
+                             configAddedTimeNs + 29 * bucketSizeNs,
+                             {configAddedTimeNs + 28 * bucketSizeNs});
+
+    EXPECT_EQ(1, data.bucket_info(13).atom_size());
+    ValidateGaugeBucketTimes(data.bucket_info(13), configAddedTimeNs + 30 * bucketSizeNs,
+                             configAddedTimeNs + 31 * bucketSizeNs,
+                             {configAddedTimeNs + 30 * bucketSizeNs});
+}
+
+TEST(GaugeMetricE2ePulledTest, TestGaugeMetricPullProbabilityWithCondition) {
+    // Initiating StatsdStats at the start of this test, so it doesn't call rand() during the test.
+    StatsdStats::getInstance();
+    // Set srand seed to make rand deterministic for testing.
+    srand(0);
+
+    auto config = CreateStatsdConfig(GaugeMetric::CONDITION_CHANGE_TO_TRUE, /*useCondition=*/true);
+    auto gaugeMetric = config.mutable_gauge_metric(0);
+    gaugeMetric->set_pull_probability(50);
+    gaugeMetric->set_max_num_gauge_atoms_per_bucket(200);
+    gaugeMetric->set_bucket(ONE_HOUR);
+
+    int64_t configAddedTimeNs = 60 * NS_PER_SEC;
+    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+    ConfigKey cfgKey;
+    auto processor =
+            CreateStatsLogProcessor(configAddedTimeNs, configAddedTimeNs, config, cfgKey,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(), ATOM_TAG);
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    // First bucket events.
+    for (int i = 0; i < 30; i++) {
+        events.push_back(CreateScreenStateChangedEvent(configAddedTimeNs + (i * 10 * NS_PER_SEC),
+                                                       android::view::DISPLAY_STATE_OFF));
+        events.push_back(CreateScreenStateChangedEvent(configAddedTimeNs + (i * 11 * NS_PER_SEC),
+                                                       android::view::DISPLAY_STATE_ON));
+    }
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + 2 * bucketSizeNs, false, true, ADB_DUMP,
+                            FAST, &buffer);
+
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ((int)gaugeMetrics.data_size(), 2);  // 2 sets of data for each pull.
+
+    // Data 1
+    auto data = gaugeMetrics.data(0);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_1",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(1, data.bucket_info_size());
+
+    // Data 1, Bucket 1
+    ASSERT_EQ(13, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+            {(int64_t)60 * NS_PER_SEC, (int64_t)80 * NS_PER_SEC, (int64_t)90 * NS_PER_SEC,
+             (int64_t)130 * NS_PER_SEC, (int64_t)150 * NS_PER_SEC, (int64_t)170 * NS_PER_SEC,
+             (int64_t)190 * NS_PER_SEC, (int64_t)200 * NS_PER_SEC, (int64_t)240 * NS_PER_SEC,
+             (int64_t)250 * NS_PER_SEC, (int64_t)300 * NS_PER_SEC, (int64_t)330 * NS_PER_SEC,
+             (int64_t)340 * NS_PER_SEC});
+
+    // Data 2
+    data = gaugeMetrics.data(1);
+    EXPECT_EQ(ATOM_TAG, data.dimensions_in_what().field());
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* subsystem name field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ("subsystem_name_2",
+              data.dimensions_in_what().value_tuple().dimensions_value(0).value_str());
+    ASSERT_EQ(1, data.bucket_info_size());
+
+    // Data 2, Bucket 1
+    ASSERT_EQ(13, data.bucket_info(0).atom_size());
+    ValidateGaugeBucketTimes(
+            data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+            {(int64_t)60 * NS_PER_SEC, (int64_t)80 * NS_PER_SEC, (int64_t)90 * NS_PER_SEC,
+             (int64_t)130 * NS_PER_SEC, (int64_t)150 * NS_PER_SEC, (int64_t)170 * NS_PER_SEC,
+             (int64_t)190 * NS_PER_SEC, (int64_t)200 * NS_PER_SEC, (int64_t)240 * NS_PER_SEC,
+             (int64_t)250 * NS_PER_SEC, (int64_t)300 * NS_PER_SEC, (int64_t)330 * NS_PER_SEC,
+             (int64_t)340 * NS_PER_SEC});
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
index 81192b2..1f35564 100644
--- a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
+++ b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
@@ -31,7 +31,6 @@
 
 StatsdConfig CreateStatsdConfigForPushedEvent(const GaugeMetric::SamplingType sampling_type) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
     *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
 
@@ -72,7 +71,6 @@
 StatsdConfig CreateStatsdConfigForRepeatedFieldsPushedEvent(
         const GaugeMetric::SamplingType sampling_type) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedAtomMatcher =
             CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
@@ -197,6 +195,7 @@
         backfillAggregatedAtoms(&reports);
         ASSERT_EQ(1, reports.reports_size());
         ASSERT_EQ(1, reports.reports(0).metrics_size());
+        EXPECT_TRUE(reports.reports(0).metrics(0).has_estimated_data_bytes());
         StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
         sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(),
                                         &gaugeMetrics);
@@ -343,7 +342,7 @@
         bool boolArray[boolArrayLength];
         boolArray[0] = 1;
         boolArray[1] = 0;
-        vector<bool> boolArrayVector = {1, 0};
+        vector<uint8_t> boolArrayVector = {1, 0};
         vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF};
 
         events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
@@ -418,7 +417,6 @@
     ShardOffsetProvider::getInstance().setShardOffset(5);
 
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher appCrashMatcher =
             CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
@@ -501,6 +499,178 @@
                              {gaugeEventTimeNs3, gaugeEventTimeNs6});
 }
 
+TEST_F(GaugeMetricE2ePushedTest, TestPushedGaugeMetricSampling) {
+    // Initiating StatsdStats at the start of this test, so it doesn't call rand() during the test.
+    StatsdStats::getInstance();
+    // Set srand seed to make rand deterministic for testing.
+    srand(0);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher appCrashMatcher =
+            CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    GaugeMetric sampledGaugeMetric =
+            createGaugeMetric("GaugeSampledAppCrashesPerUid", appCrashMatcher.id(),
+                              GaugeMetric::FIRST_N_SAMPLES, nullopt, nullopt);
+    *sampledGaugeMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /* uid */});
+    sampledGaugeMetric.set_sampling_percentage(50);
+    *config.add_gauge_metric() = sampledGaugeMetric;
+
+    const int64_t configAddedTimeNs = 10 * NS_PER_SEC;  // 0:10
+    const int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            configAddedTimeNs, configAddedTimeNs, config, cfgKey, nullptr, 0, new UidMap());
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    for (int i = 0; i < 10; i++) {
+        events.push_back(
+                CreateAppCrashOccurredEvent(configAddedTimeNs + (10 * i * NS_PER_SEC), 1000 + i));
+    }
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_gauge_metrics());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ(5, gaugeMetrics.data_size());
+
+    GaugeMetricData data = gaugeMetrics.data(0);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, 1000);
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs, {configAddedTimeNs});
+
+    data = gaugeMetrics.data(1);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, 1002);
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs,
+                             {configAddedTimeNs + (10 * 2 * NS_PER_SEC)});
+
+    data = gaugeMetrics.data(2);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, 1003);
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs,
+                             {configAddedTimeNs + (10 * 3 * NS_PER_SEC)});
+
+    data = gaugeMetrics.data(3);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, 1007);
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs,
+                             {configAddedTimeNs + (10 * 7 * NS_PER_SEC)});
+
+    data = gaugeMetrics.data(4);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, 1009);
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs,
+                             {configAddedTimeNs + (10 * 9 * NS_PER_SEC)});
+}
+
+TEST_F(GaugeMetricE2ePushedTest, TestPushedGaugeMetricSamplingWithDimensionalSampling) {
+    ShardOffsetProvider::getInstance().setShardOffset(5);
+    // Initiating StatsdStats at the start of this test, so it doesn't call rand() during the test.
+    StatsdStats::getInstance();
+    // Set srand seed to make rand deterministic for testing.
+    srand(0);
+
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher appCrashMatcher =
+            CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
+    *config.add_atom_matcher() = appCrashMatcher;
+
+    GaugeMetric sampledGaugeMetric =
+            createGaugeMetric("GaugeSampledAppCrashesPerUid", appCrashMatcher.id(),
+                              GaugeMetric::FIRST_N_SAMPLES, nullopt, nullopt);
+    *sampledGaugeMetric.mutable_dimensions_in_what() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /* uid */});
+    *sampledGaugeMetric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateDimensions(util::APP_CRASH_OCCURRED, {1 /*uid*/});
+    sampledGaugeMetric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    sampledGaugeMetric.set_sampling_percentage(50);
+    *config.add_gauge_metric() = sampledGaugeMetric;
+
+    const int64_t configAddedTimeNs = 10 * NS_PER_SEC;  // 0:10
+    const int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
+            configAddedTimeNs, configAddedTimeNs, config, cfgKey, nullptr, 0, new UidMap());
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    for (int i = 0; i < 30; i++) {
+        // Generate events with three different app uids: 1001, 1002, 1003.
+        events.push_back(CreateAppCrashOccurredEvent(configAddedTimeNs + (10 * i * NS_PER_SEC),
+                                                     1001 + (i % 3)));
+    }
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_gauge_metrics());
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+    ASSERT_EQ(2, gaugeMetrics.data_size());
+
+    // Only Uid 1 and 3 are logged. (odd hash value) + (offset of 5) % (shard count of 2) = 0
+    GaugeMetricData data = gaugeMetrics.data(0);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, 1001);
+    ValidateGaugeBucketTimes(
+            data.bucket_info(0), configAddedTimeNs, configAddedTimeNs + bucketSizeNs,
+            {10 * NS_PER_SEC, 40 * NS_PER_SEC, 220 * NS_PER_SEC, 280 * NS_PER_SEC});
+
+    data = gaugeMetrics.data(1);
+    ValidateUidDimension(data.dimensions_in_what(), util::APP_CRASH_OCCURRED, 1003);
+    ValidateGaugeBucketTimes(data.bucket_info(0), configAddedTimeNs,
+                             configAddedTimeNs + bucketSizeNs,
+                             {60 * NS_PER_SEC, 120 * NS_PER_SEC, 150 * NS_PER_SEC, 180 * NS_PER_SEC,
+                              210 * NS_PER_SEC, 300 * NS_PER_SEC});
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/KllMetric_e2e_test.cpp b/statsd/tests/e2e/KllMetric_e2e_test.cpp
index a6100c2..7cb5807 100644
--- a/statsd/tests/e2e/KllMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/KllMetric_e2e_test.cpp
@@ -50,8 +50,6 @@
         metric = createKllMetric("ScreenBrightness", whatMatcher, /*valueField=*/1,
                                  /*condition=*/nullopt);
 
-        config.add_allowed_log_source("AID_ROOT");
-
         *config.add_atom_matcher() = whatMatcher;
         *config.add_kll_metric() = metric;
 
@@ -93,6 +91,7 @@
     ConfigMetricsReport report = reports.reports(0);
     ASSERT_EQ(report.metrics_size(), 1);
     StatsLogReport metricReport = report.metrics(0);
+    EXPECT_TRUE(metricReport.has_estimated_data_bytes());
     EXPECT_EQ(metricReport.metric_id(), metric.id());
     EXPECT_TRUE(metricReport.has_kll_metrics());
     ASSERT_EQ(metricReport.kll_metrics().data_size(), 1);
@@ -177,7 +176,6 @@
 TEST_F(KllMetricE2eTest, TestInitWithKllFieldPositionALL) {
     // Create config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedMatcher =
             CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
@@ -209,7 +207,6 @@
 
     // Create config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher bleScanResultReceivedMatcher = CreateSimpleAtomMatcher(
             "BleScanResultReceivedAtomMatcher", util::BLE_SCAN_RESULT_RECEIVED);
diff --git a/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/statsd/tests/e2e/MetricActivation_e2e_test.cpp
index 5452636..6da3f99 100644
--- a/statsd/tests/e2e/MetricActivation_e2e_test.cpp
+++ b/statsd/tests/e2e/MetricActivation_e2e_test.cpp
@@ -30,7 +30,6 @@
 
 StatsdConfig CreateStatsdConfig() {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher();
     auto crashMatcher = CreateProcessCrashAtomMatcher();
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
@@ -62,7 +61,6 @@
 
 StatsdConfig CreateStatsdConfigWithOneDeactivation() {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher();
     auto crashMatcher = CreateProcessCrashAtomMatcher();
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
@@ -97,7 +95,6 @@
 
 StatsdConfig CreateStatsdConfigWithTwoDeactivations() {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher();
     auto crashMatcher = CreateProcessCrashAtomMatcher();
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
@@ -136,7 +133,6 @@
 
 StatsdConfig CreateStatsdConfigWithSameDeactivations() {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher();
     auto crashMatcher = CreateProcessCrashAtomMatcher();
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
@@ -172,7 +168,6 @@
 
 StatsdConfig CreateStatsdConfigWithTwoMetricsTwoDeactivations() {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher();
     auto crashMatcher = CreateProcessCrashAtomMatcher();
     auto foregroundMatcher = CreateMoveToForegroundAtomMatcher();
@@ -267,7 +262,7 @@
                                               activeConfigs.end());
                 return true;
             },
-            mockLogEventFilter);
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
@@ -485,7 +480,7 @@
                                               activeConfigs.end());
                 return true;
             },
-            mockLogEventFilter);
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
@@ -813,7 +808,7 @@
                                               activeConfigs.end());
                 return true;
             },
-            mockLogEventFilter);
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
@@ -1153,7 +1148,7 @@
                                               activeConfigs.end());
                 return true;
             },
-            mockLogEventFilter);
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
@@ -1357,7 +1352,7 @@
                                               activeConfigs.end());
                 return true;
             },
-            mockLogEventFilter);
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
 
     const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
     EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
diff --git a/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
index 5e77ee0..bdf1a46 100644
--- a/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
+++ b/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp
@@ -29,7 +29,6 @@
 
 StatsdConfig CreateStatsdConfig() {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
 
     *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
diff --git a/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index c9d7c48..c3503ea 100644
--- a/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -37,7 +37,6 @@
 
 StatsdConfig MakeCountMetricConfig(const std::optional<bool> splitBucket) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto appCrashMatcher = CreateProcessCrashAtomMatcher();
     *config.add_atom_matcher() = appCrashMatcher;
@@ -53,7 +52,6 @@
 
 StatsdConfig MakeValueMetricConfig(int64_t minTime) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     auto pulledAtomMatcher =
@@ -79,7 +77,6 @@
 
 StatsdConfig MakeGaugeMetricConfig(int64_t minTime) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     auto pulledAtomMatcher =
diff --git a/statsd/tests/e2e/RestrictedConfig_e2e_test.cpp b/statsd/tests/e2e/RestrictedConfig_e2e_test.cpp
new file mode 100644
index 0000000..5d6a05e
--- /dev/null
+++ b/statsd/tests/e2e/RestrictedConfig_e2e_test.cpp
@@ -0,0 +1,474 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "flags/FlagProvider.h"
+#include "storage/StorageManager.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+const int32_t atomTag = 666;
+const string delegatePackageName = "com.test.restricted.metrics.package";
+const int32_t delegateUid = 10200;
+const string configPackageName = "com.test.config.package";
+int64_t metricId;
+int64_t anotherMetricId;
+
+StatsdConfig CreateConfigWithOneMetric() {
+    StatsdConfig config;
+    AtomMatcher atomMatcher = CreateSimpleAtomMatcher("testmatcher", atomTag);
+    *config.add_atom_matcher() = atomMatcher;
+
+    EventMetric eventMetric = createEventMetric("EventMetric", atomMatcher.id(), nullopt);
+    metricId = eventMetric.id();
+    *config.add_event_metric() = eventMetric;
+    return config;
+}
+StatsdConfig CreateConfigWithTwoMetrics() {
+    StatsdConfig config;
+    AtomMatcher atomMatcher = CreateSimpleAtomMatcher("testmatcher", atomTag);
+    *config.add_atom_matcher() = atomMatcher;
+
+    EventMetric eventMetric = createEventMetric("EventMetric", atomMatcher.id(), nullopt);
+    metricId = eventMetric.id();
+    *config.add_event_metric() = eventMetric;
+    EventMetric anotherEventMetric =
+            createEventMetric("AnotherEventMetric", atomMatcher.id(), nullopt);
+    anotherMetricId = anotherEventMetric.id();
+    *config.add_event_metric() = anotherEventMetric;
+    return config;
+}
+
+std::vector<std::unique_ptr<LogEvent>> CreateLogEvents(int64_t configAddedTimeNs) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateNonRestrictedLogEvent(atomTag, configAddedTimeNs + 10 * NS_PER_SEC));
+    events.push_back(CreateNonRestrictedLogEvent(atomTag, configAddedTimeNs + 20 * NS_PER_SEC));
+    events.push_back(CreateNonRestrictedLogEvent(atomTag, configAddedTimeNs + 30 * NS_PER_SEC));
+    return events;
+}
+
+}  // Anonymous namespace
+
+class RestrictedConfigE2ETest : public StatsServiceConfigTest {
+protected:
+    shared_ptr<MockStatsQueryCallback> mockStatsQueryCallback;
+    const ConfigKey configKey = ConfigKey(kCallingUid, kConfigKey);
+    vector<string> queryDataResult;
+    vector<string> columnNamesResult;
+    vector<int32_t> columnTypesResult;
+    int32_t rowCountResult = 0;
+    string error;
+
+    void SetUp() override {
+        if (!isAtLeastU()) {
+            GTEST_SKIP();
+        }
+        StatsServiceConfigTest::SetUp();
+
+        mockStatsQueryCallback = SharedRefBase::make<StrictMock<MockStatsQueryCallback>>();
+        EXPECT_CALL(*mockStatsQueryCallback, sendResults(_, _, _, _))
+                .Times(AnyNumber())
+                .WillRepeatedly(Invoke(
+                        [this](const vector<string>& queryData, const vector<string>& columnNames,
+                               const vector<int32_t>& columnTypes, int32_t rowCount) {
+                            queryDataResult = queryData;
+                            columnNamesResult = columnNames;
+                            columnTypesResult = columnTypes;
+                            rowCountResult = rowCount;
+                            error = "";
+                            return Status::ok();
+                        }));
+        EXPECT_CALL(*mockStatsQueryCallback, sendFailure(_))
+                .Times(AnyNumber())
+                .WillRepeatedly(Invoke([this](const string& err) {
+                    error = err;
+                    queryDataResult.clear();
+                    columnNamesResult.clear();
+                    columnTypesResult.clear();
+                    rowCountResult = 0;
+                    return Status::ok();
+                }));
+
+        int64_t startTimeNs = getElapsedRealtimeNs();
+        UidData uidData;
+        *uidData.add_app_info() = createApplicationInfo(delegateUid, 1, "v2", delegatePackageName);
+        *uidData.add_app_info() = createApplicationInfo(kCallingUid, 1, "v2", configPackageName);
+        service->mUidMap->updateMap(startTimeNs, uidData);
+    }
+    void TearDown() override {
+        if (!isAtLeastU()) {
+            GTEST_SKIP();
+        }
+        Mock::VerifyAndClear(mockStatsQueryCallback.get());
+        queryDataResult.clear();
+        columnNamesResult.clear();
+        columnTypesResult.clear();
+        rowCountResult = 0;
+        error = "";
+        StatsServiceConfigTest::TearDown();
+        FlagProvider::getInstance().resetOverrides();
+        dbutils::deleteDb(configKey);
+    }
+
+    void verifyRestrictedData(int32_t expectedNumOfMetrics, int64_t metricIdToVerify = metricId,
+                              bool shouldExist = true) {
+        std::stringstream query;
+        query << "SELECT * FROM metric_" << dbutils::reformatMetricId(metricIdToVerify);
+        string err;
+        std::vector<int32_t> columnTypes;
+        std::vector<string> columnNames;
+        std::vector<std::vector<std::string>> rows;
+        if (shouldExist) {
+            EXPECT_TRUE(
+                    dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+            EXPECT_EQ(rows.size(), expectedNumOfMetrics);
+        } else {
+            // Expect that table is deleted.
+            EXPECT_FALSE(
+                    dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+        }
+    }
+};
+
+TEST_F(RestrictedConfigE2ETest, RestrictedConfigNoReport) {
+    StatsdConfig config = CreateConfigWithOneMetric();
+    config.set_restricted_metrics_delegate_package_name("delegate");
+    sendConfig(config);
+    int64_t configAddedTimeNs = getElapsedRealtimeNs();
+
+    for (auto& event : CreateLogEvents(configAddedTimeNs)) {
+        service->OnLogEvent(event.get());
+    }
+
+    vector<uint8_t> output;
+    ConfigKey configKey(kCallingUid, kConfigKey);
+    service->getData(kConfigKey, kCallingUid, &output);
+
+    EXPECT_TRUE(output.empty());
+}
+
+TEST_F(RestrictedConfigE2ETest, NonRestrictedConfigGetReport) {
+    StatsdConfig config = CreateConfigWithOneMetric();
+    sendConfig(config);
+    int64_t configAddedTimeNs = getElapsedRealtimeNs();
+
+    for (auto& event : CreateLogEvents(configAddedTimeNs)) {
+        service->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReport report = getReports(service->mProcessor, /*timestamp=*/10);
+    EXPECT_EQ(report.metrics_size(), 1);
+}
+
+TEST_F(RestrictedConfigE2ETest, RestrictedShutdownFlushToRestrictedDB) {
+    StatsdConfig config = CreateConfigWithOneMetric();
+    config.set_restricted_metrics_delegate_package_name("delegate");
+    sendConfig(config);
+    int64_t configAddedTimeNs = getElapsedRealtimeNs();
+    std::vector<std::unique_ptr<LogEvent>> logEvents = CreateLogEvents(configAddedTimeNs);
+    for (const auto& e : logEvents) {
+        service->OnLogEvent(e.get());
+    }
+
+    service->informDeviceShutdown();
+
+    // Should not be written to non-restricted storage.
+    EXPECT_FALSE(StorageManager::hasConfigMetricsReport(ConfigKey(kCallingUid, kConfigKey)));
+    verifyRestrictedData(logEvents.size());
+}
+
+TEST_F(RestrictedConfigE2ETest, NonRestrictedOnShutdownWriteDataToDisk) {
+    StatsdConfig config = CreateConfigWithOneMetric();
+    sendConfig(config);
+    int64_t configAddedTimeNs = getElapsedRealtimeNs();
+    for (auto& event : CreateLogEvents(configAddedTimeNs)) {
+        service->OnLogEvent(event.get());
+    }
+
+    service->informDeviceShutdown();
+
+    EXPECT_TRUE(StorageManager::hasConfigMetricsReport(ConfigKey(kCallingUid, kConfigKey)));
+}
+
+TEST_F(RestrictedConfigE2ETest, RestrictedConfigOnTerminateFlushToRestrictedDB) {
+    StatsdConfig config = CreateConfigWithOneMetric();
+    config.set_restricted_metrics_delegate_package_name("delegate");
+    sendConfig(config);
+    int64_t configAddedTimeNs = getElapsedRealtimeNs();
+    std::vector<std::unique_ptr<LogEvent>> logEvents = CreateLogEvents(configAddedTimeNs);
+    for (auto& event : logEvents) {
+        service->OnLogEvent(event.get());
+    }
+
+    service->Terminate();
+
+    EXPECT_FALSE(StorageManager::hasConfigMetricsReport(ConfigKey(kCallingUid, kConfigKey)));
+    verifyRestrictedData(logEvents.size());
+}
+
+TEST_F(RestrictedConfigE2ETest, NonRestrictedConfigOnTerminateWriteDataToDisk) {
+    StatsdConfig config = CreateConfigWithOneMetric();
+    sendConfig(config);
+    int64_t configAddedTimeNs = getElapsedRealtimeNs();
+    for (auto& event : CreateLogEvents(configAddedTimeNs)) {
+        service->OnLogEvent(event.get());
+    }
+
+    service->Terminate();
+
+    EXPECT_TRUE(StorageManager::hasConfigMetricsReport(ConfigKey(kCallingUid, kConfigKey)));
+}
+
+TEST_F(RestrictedConfigE2ETest, RestrictedConfigOnUpdateWithMetricRemoval) {
+    StatsdConfig complexConfig = CreateConfigWithTwoMetrics();
+    complexConfig.set_restricted_metrics_delegate_package_name(delegatePackageName);
+    sendConfig(complexConfig);
+    int64_t configAddedTimeNs = getElapsedRealtimeNs();
+    std::vector<std::unique_ptr<LogEvent>> logEvents = CreateLogEvents(configAddedTimeNs);
+    for (auto& event : logEvents) {
+        service->OnLogEvent(event.get());
+    }
+
+    // Use query API to make sure data is flushed.
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(metricId);
+    service->querySql(query.str(), /*minSqlClientVersion=*/0,
+                      /*policyConfig=*/{}, mockStatsQueryCallback,
+                      /*configKey=*/kConfigKey, /*configPackage=*/configPackageName,
+                      /*callingUid=*/delegateUid);
+    EXPECT_EQ(error, "");
+    EXPECT_EQ(rowCountResult, logEvents.size());
+    verifyRestrictedData(logEvents.size(), anotherMetricId, true);
+
+    // Update config to have only one metric
+    StatsdConfig config = CreateConfigWithOneMetric();
+    config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+    sendConfig(config);
+
+    // Make sure metric data is deleted.
+    verifyRestrictedData(logEvents.size(), metricId, true);
+    verifyRestrictedData(logEvents.size(), anotherMetricId, false);
+}
+
+TEST_F(RestrictedConfigE2ETest, TestSendRestrictedMetricsChangedBroadcast) {
+    vector<int64_t> receivedMetricIds;
+    int receiveCount = 0;
+    shared_ptr<MockPendingIntentRef> pir = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir, sendRestrictedMetricsChangedBroadcast(_))
+            .Times(7)
+            .WillRepeatedly(Invoke([&receivedMetricIds, &receiveCount](const vector<int64_t>& ids) {
+                receiveCount++;
+                receivedMetricIds = ids;
+                return Status::ok();
+            }));
+
+    // Set the operation. No configs present so empty list is returned.
+    vector<int64_t> returnedMetricIds;
+    service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName, pir, delegateUid,
+                                                  &returnedMetricIds);
+    EXPECT_EQ(receiveCount, 0);
+    EXPECT_THAT(returnedMetricIds, IsEmpty());
+
+    // Add restricted config. Should receive one metric
+    StatsdConfig config = CreateConfigWithOneMetric();
+    config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+    sendConfig(config);
+    EXPECT_EQ(receiveCount, 1);
+    EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId));
+
+    // Config update, should receive two metrics.
+    config = CreateConfigWithTwoMetrics();
+    config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+    sendConfig(config);
+    EXPECT_EQ(receiveCount, 2);
+    EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId, anotherMetricId));
+
+    // Make config unrestricted. Should receive empty list.
+    config.clear_restricted_metrics_delegate_package_name();
+    sendConfig(config);
+    EXPECT_EQ(receiveCount, 3);
+    EXPECT_THAT(receivedMetricIds, IsEmpty());
+
+    // Update the unrestricted config. Nothing should be sent.
+    config = CreateConfigWithOneMetric();
+    sendConfig(config);
+
+    // Update config and make it restricted. Should receive one metric.
+    config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+    sendConfig(config);
+    EXPECT_EQ(receiveCount, 4);
+    EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId));
+
+    // Send an invalid config. Should receive empty list.
+    auto invalidCountMetric = config.add_count_metric();
+    invalidCountMetric->set_what(0);
+    sendConfig(config);
+    EXPECT_EQ(receiveCount, 5);
+    EXPECT_THAT(receivedMetricIds, IsEmpty());
+
+    service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName, delegateUid);
+
+    // Nothing should be sent since the operation is removed.
+    config = CreateConfigWithTwoMetrics();
+    config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+    sendConfig(config);
+
+    // Set the operation. Two metrics should be returned.
+    returnedMetricIds.clear();
+    service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName, pir, delegateUid,
+                                                  &returnedMetricIds);
+    EXPECT_THAT(returnedMetricIds, UnorderedElementsAre(metricId, anotherMetricId));
+    EXPECT_EQ(receiveCount, 5);
+
+    // Config update, should receive two metrics.
+    config = CreateConfigWithOneMetric();
+    config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+    sendConfig(config);
+    EXPECT_EQ(receiveCount, 6);
+    EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId));
+
+    // Remove the config and verify an empty list is received
+    service->removeConfiguration(kConfigKey, kCallingUid);
+    EXPECT_EQ(receiveCount, 7);
+    EXPECT_THAT(receivedMetricIds, IsEmpty());
+
+    // Cleanup.
+    service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName, delegateUid);
+}
+
+TEST_F(RestrictedConfigE2ETest, TestSendRestrictedMetricsChangedBroadcastMultipleListeners) {
+    const string configPackageName2 = "com.test.config.package2";
+    const int32_t delegateUid2 = delegateUid + 1, delegateUid3 = delegateUid + 2;
+    service->informOnePackage(configPackageName2, kCallingUid, 0, "", "", {});
+    service->informOnePackage(delegatePackageName, delegateUid2, 0, "", "", {});
+    service->informOnePackage("not.a.good.package", delegateUid3, 0, "", "", {});
+
+    vector<int64_t> receivedMetricIds;
+    int receiveCount = 0;
+    shared_ptr<MockPendingIntentRef> pir = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir, sendRestrictedMetricsChangedBroadcast(_))
+            .Times(2)
+            .WillRepeatedly(Invoke([&receivedMetricIds, &receiveCount](const vector<int64_t>& ids) {
+                receiveCount++;
+                receivedMetricIds = ids;
+                return Status::ok();
+            }));
+
+    int receiveCount2 = 0;
+    vector<int64_t> receivedMetricIds2;
+    shared_ptr<MockPendingIntentRef> pir2 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir2, sendRestrictedMetricsChangedBroadcast(_))
+            .Times(2)
+            .WillRepeatedly(
+                    Invoke([&receivedMetricIds2, &receiveCount2](const vector<int64_t>& ids) {
+                        receiveCount2++;
+                        receivedMetricIds2 = ids;
+                        return Status::ok();
+                    }));
+
+    // This one should never be called.
+    shared_ptr<MockPendingIntentRef> pir3 = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    EXPECT_CALL(*pir3, sendRestrictedMetricsChangedBroadcast(_)).Times(0);
+
+    // Set the operations. No configs present so empty list is returned.
+    vector<int64_t> returnedMetricIds;
+    service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName, pir, delegateUid,
+                                                  &returnedMetricIds);
+    EXPECT_EQ(receiveCount, 0);
+    EXPECT_THAT(returnedMetricIds, IsEmpty());
+
+    vector<int64_t> returnedMetricIds2;
+    service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName2, pir2,
+                                                  delegateUid2, &returnedMetricIds2);
+    EXPECT_EQ(receiveCount2, 0);
+    EXPECT_THAT(returnedMetricIds2, IsEmpty());
+
+    // Represents a package listening for changes but doesn't match the restricted package in the
+    // config.
+    vector<int64_t> returnedMetricIds3;
+    service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName, pir3, delegateUid3,
+                                                  &returnedMetricIds3);
+    EXPECT_THAT(returnedMetricIds3, IsEmpty());
+
+    // Add restricted config. Should receive one metric on pir1 and 2.
+    StatsdConfig config = CreateConfigWithOneMetric();
+    config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+    sendConfig(config);
+    EXPECT_EQ(receiveCount, 1);
+    EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId));
+    EXPECT_EQ(receiveCount2, 1);
+    EXPECT_THAT(receivedMetricIds2, UnorderedElementsAre(metricId));
+
+    // Config update, should receive two metrics on pir1 and 2.
+    config = CreateConfigWithTwoMetrics();
+    config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+    sendConfig(config);
+    EXPECT_EQ(receiveCount, 2);
+    EXPECT_THAT(receivedMetricIds, UnorderedElementsAre(metricId, anotherMetricId));
+    EXPECT_EQ(receiveCount2, 2);
+    EXPECT_THAT(receivedMetricIds2, UnorderedElementsAre(metricId, anotherMetricId));
+
+    // Cleanup.
+    service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName, delegateUid);
+    service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName2, delegateUid2);
+    service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName, delegateUid3);
+}
+
+TEST_F(RestrictedConfigE2ETest, TestSendRestrictedMetricsChangedBroadcastMultipleMatchedConfigs) {
+    const int32_t callingUid2 = kCallingUid + 1;
+    service->informOnePackage(configPackageName, callingUid2, 0, "", "", {});
+
+    // Add restricted config.
+    StatsdConfig config = CreateConfigWithOneMetric();
+    config.set_restricted_metrics_delegate_package_name(delegatePackageName);
+    sendConfig(config);
+
+    // Add a second config.
+    const int64_t metricId2 = 42;
+    config.mutable_event_metric(0)->set_id(42);
+    string str;
+    config.SerializeToString(&str);
+    std::vector<uint8_t> configAsVec(str.begin(), str.end());
+    service->addConfiguration(kConfigKey, configAsVec, callingUid2);
+
+    // Set the operation. Matches multiple configs so a union of metrics are returned.
+    shared_ptr<MockPendingIntentRef> pir = SharedRefBase::make<StrictMock<MockPendingIntentRef>>();
+    vector<int64_t> returnedMetricIds;
+    service->setRestrictedMetricsChangedOperation(kConfigKey, configPackageName, pir, delegateUid,
+                                                  &returnedMetricIds);
+    EXPECT_THAT(returnedMetricIds, UnorderedElementsAre(metricId, metricId2));
+
+    // Cleanup.
+    service->removeRestrictedMetricsChangedOperation(kConfigKey, configPackageName, delegateUid);
+
+    ConfigKey cfgKey(callingUid2, kConfigKey);
+    service->removeConfiguration(kConfigKey, callingUid2);
+    service->mProcessor->onDumpReport(cfgKey, getElapsedRealtimeNs(),
+                                      false /* include_current_bucket*/, true /* erase_data */,
+                                      ADB_DUMP, NO_TIME_CONSTRAINTS, nullptr);
+}
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/tests/e2e/RestrictedEventMetric_e2e_test.cpp b/statsd/tests/e2e/RestrictedEventMetric_e2e_test.cpp
new file mode 100644
index 0000000..d561769
--- /dev/null
+++ b/statsd/tests/e2e/RestrictedEventMetric_e2e_test.cpp
@@ -0,0 +1,1189 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "flags/FlagProvider.h"
+#include "src/StatsLogProcessor.h"
+#include "src/state/StateTracker.h"
+#include "src/stats_log_util.h"
+#include "src/storage/StorageManager.h"
+#include "src/utils/RestrictedPolicyManager.h"
+#include "stats_annotations.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using base::StringPrintf;
+
+#ifdef __ANDROID__
+
+namespace {
+const int64_t oneMonthLater = getWallClockNs() + 31 * 24 * 3600 * NS_PER_SEC;
+const int64_t configId = 12345;
+const string delegate_package_name = "com.test.restricted.metrics.package";
+const int32_t delegate_uid = 1005;
+const string config_package_name = "com.test.config.package";
+const int32_t config_app_uid = 123;
+const ConfigKey configKey(config_app_uid, configId);
+const int64_t eightDaysAgo = getWallClockNs() - 8 * 24 * 3600 * NS_PER_SEC;
+const int64_t oneDayAgo = getWallClockNs() - 1 * 24 * 3600 * NS_PER_SEC;
+}  // anonymous namespace
+
+// Setup for test fixture.
+class RestrictedEventMetricE2eTest : public ::testing::Test {
+protected:
+    shared_ptr<MockStatsQueryCallback> mockStatsQueryCallback;
+    vector<string> queryDataResult;
+    vector<string> columnNamesResult;
+    vector<int32_t> columnTypesResult;
+    int32_t rowCountResult = 0;
+    string error;
+    sp<UidMap> uidMap;
+    sp<StatsLogProcessor> processor;
+    int32_t atomTag;
+    int64_t restrictedMetricId;
+    int64_t configAddedTimeNs;
+    StatsdConfig config;
+
+private:
+    void SetUp() override {
+        if (!isAtLeastU()) {
+            GTEST_SKIP();
+        }
+
+        mockStatsQueryCallback = SharedRefBase::make<StrictMock<MockStatsQueryCallback>>();
+        EXPECT_CALL(*mockStatsQueryCallback, sendResults(_, _, _, _))
+                .Times(AnyNumber())
+                .WillRepeatedly(Invoke(
+                        [this](const vector<string>& queryData, const vector<string>& columnNames,
+                               const vector<int32_t>& columnTypes, int32_t rowCount) {
+                            queryDataResult = queryData;
+                            columnNamesResult = columnNames;
+                            columnTypesResult = columnTypes;
+                            rowCountResult = rowCount;
+                            error = "";
+                            return Status::ok();
+                        }));
+        EXPECT_CALL(*mockStatsQueryCallback, sendFailure(_))
+                .Times(AnyNumber())
+                .WillRepeatedly(Invoke([this](const string& err) {
+                    error = err;
+                    queryDataResult.clear();
+                    columnNamesResult.clear();
+                    columnTypesResult.clear();
+                    rowCountResult = 0;
+                    return Status::ok();
+                }));
+
+        atomTag = 999;
+        AtomMatcher restrictedAtomMatcher = CreateSimpleAtomMatcher("restricted_matcher", atomTag);
+        *config.add_atom_matcher() = restrictedAtomMatcher;
+
+        EventMetric restrictedEventMetric =
+                createEventMetric("RestrictedMetricLogged", restrictedAtomMatcher.id(), nullopt);
+        *config.add_event_metric() = restrictedEventMetric;
+        restrictedMetricId = restrictedEventMetric.id();
+
+        config.set_restricted_metrics_delegate_package_name(delegate_package_name.c_str());
+
+        const int64_t baseTimeNs = 0;                     // 0:00
+        configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC;  // 0:01
+
+        uidMap = new UidMap();
+        uidMap->updateApp(configAddedTimeNs, delegate_package_name,
+                          /*uid=*/delegate_uid, /*versionCode=*/1,
+                          /*versionString=*/"v2",
+                          /*installer=*/"", /*certificateHash=*/{});
+        uidMap->updateApp(configAddedTimeNs + 1, config_package_name,
+                          /*uid=*/config_app_uid, /*versionCode=*/1,
+                          /*versionString=*/"v2",
+                          /*installer=*/"", /*certificateHash=*/{});
+
+        processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, configKey,
+                                            /*puller=*/nullptr, /*atomTag=*/0, uidMap);
+    }
+
+    void TearDown() override {
+        Mock::VerifyAndClear(mockStatsQueryCallback.get());
+        queryDataResult.clear();
+        columnNamesResult.clear();
+        columnTypesResult.clear();
+        rowCountResult = 0;
+        error = "";
+        dbutils::deleteDb(configKey);
+        dbutils::deleteDb(ConfigKey(config_app_uid + 1, configId));
+        FlagProvider::getInstance().resetOverrides();
+    }
+};
+
+TEST_F(RestrictedEventMetricE2eTest, TestQueryThreeEvents) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 200));
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 300));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_EQ(rowCountResult, 3);
+    EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+                                             _,  // wallClockNs
+                                             _,  // field_1
+                                             to_string(atomTag), to_string(configAddedTimeNs + 200),
+                                             _,  // wallClockNs
+                                             _,  // field_1
+                                             to_string(atomTag), to_string(configAddedTimeNs + 300),
+                                             _,  // wallClockNs
+                                             _   // field_1
+                                             ));
+
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestInvalidSchemaIncreasingFieldCount) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomTag);
+    AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+                                   ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+    AStatsEvent_overwriteTimestamp(statsEvent, configAddedTimeNs + 200);
+    // This event has two extra fields
+    AStatsEvent_writeString(statsEvent, "111");
+    AStatsEvent_writeInt32(statsEvent, 11);
+    AStatsEvent_writeFloat(statsEvent, 11.0);
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+    events.push_back(std::move(logEvent));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+        processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST,
+                                   event->GetElapsedTimestampNs() + 20 * NS_PER_SEC,
+                                   getWallClockNs());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_EQ(rowCountResult, 1);
+    // Event 2 rejected.
+    EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+                                             _,  // wallClockNs
+                                             _   // field_1
+                                             ));
+
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestInvalidSchemaDecreasingFieldCount) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomTag);
+    AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+                                   ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+    AStatsEvent_overwriteTimestamp(statsEvent, configAddedTimeNs + 100);
+    // This event has one extra field.
+    AStatsEvent_writeString(statsEvent, "111");
+    AStatsEvent_writeInt32(statsEvent, 11);
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+
+    events.push_back(std::move(logEvent));
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 200));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+        processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST,
+                                   event->GetElapsedTimestampNs() + 20 * NS_PER_SEC,
+                                   getWallClockNs());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_EQ(rowCountResult, 1);
+    // Event 2 Rejected
+    EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+                                             _,             // wallClockNs
+                                             "111",         // field_1
+                                             to_string(11)  // field_2
+                                             ));
+
+    EXPECT_THAT(columnNamesResult, ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs",
+                                               "field_1", "field_2"));
+
+    EXPECT_THAT(columnTypesResult, ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER,
+                                               SQLITE_TEXT, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestInvalidSchemaDifferentFieldType) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomTag);
+    AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+                                   ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+    AStatsEvent_overwriteTimestamp(statsEvent, configAddedTimeNs + 200);
+    // This event has a string instead of an int field
+    AStatsEvent_writeString(statsEvent, "test_string");
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+    events.push_back(std::move(logEvent));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+        processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST,
+                                   event->GetElapsedTimestampNs() + 20 * NS_PER_SEC,
+                                   getWallClockNs());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_EQ(rowCountResult, 1);
+    // Event 2 rejected.
+    EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+                                             _,  // wallClockNs
+                                             _   // field_1
+                                             ));
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestNewMetricSchemaAcrossReboot) {
+    int64_t currentWallTimeNs = getWallClockNs();
+    int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+    processor->OnLogEvent(event1.get());
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult,
+                ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+                            _,  // wallTimestampNs
+                            _   // field_1
+                            ));
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+
+    // Create a new processor to simulate a reboot
+    auto processor2 =
+            CreateStatsLogProcessor(/*baseTimeNs=*/0, configAddedTimeNs, config, configKey,
+                                    /*puller=*/nullptr, /*atomTag=*/0, uidMap);
+
+    // Create a restricted event with one extra field.
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomTag);
+    AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+                                   ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+    AStatsEvent_overwriteTimestamp(statsEvent, originalEventElapsedTime + 100);
+    // This event has one extra field.
+    AStatsEvent_writeString(statsEvent, "111");
+    AStatsEvent_writeInt32(statsEvent, 11);
+    std::unique_ptr<LogEvent> event2 = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, event2.get());
+    processor2->OnLogEvent(event2.get());
+
+    processor2->querySql(query.str(), /*minSqlClientVersion=*/0,
+                         /*policyConfig=*/{}, mockStatsQueryCallback,
+                         /*configKey=*/configId, /*configPackage=*/config_package_name,
+                         /*callingUid=*/delegate_uid);
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult,
+                ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime + 100),
+                            _,               // wallTimestampNs
+                            to_string(111),  // field_1
+                            to_string(11)    // field_2
+                            ));
+    EXPECT_THAT(columnNamesResult, ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs",
+                                               "field_1", "field_2"));
+    EXPECT_THAT(columnTypesResult, ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER,
+                                               SQLITE_TEXT, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestOneEventMultipleUids) {
+    uidMap->updateApp(configAddedTimeNs, delegate_package_name,
+                      /*uid=*/delegate_uid + 1, /*versionCode=*/1,
+                      /*versionString=*/"v2",
+                      /*installer=*/"", /*certificateHash=*/{});
+    uidMap->updateApp(configAddedTimeNs + 1, config_package_name,
+                      /*uid=*/config_app_uid + 1, /*versionCode=*/1,
+                      /*versionString=*/"v2",
+                      /*installer=*/"", /*certificateHash=*/{});
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+                                             _,  // wallClockNs
+                                             _   // field_1
+                                             ));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestOneEventStaticUid) {
+    ConfigKey key2(2000, configId);  // shell uid
+    processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, key2, config);
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/"AID_SHELL",
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+                                             _,  // wallClockNs
+                                             _   // field_1
+                                             ));
+    dbutils::deleteDb(key2);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestTooManyConfigsAmbiguousQuery) {
+    ConfigKey key2(config_app_uid + 1, configId);
+    processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, key2, config);
+
+    uidMap->updateApp(configAddedTimeNs, delegate_package_name,
+                      /*uid=*/delegate_uid + 1, /*versionCode=*/1,
+                      /*versionString=*/"v2",
+                      /*installer=*/"", /*certificateHash=*/{});
+    uidMap->updateApp(configAddedTimeNs + 1, config_package_name.c_str(),
+                      /*uid=*/config_app_uid + 1, /*versionCode=*/1,
+                      /*versionString=*/"v2",
+                      /*installer=*/"", /*certificateHash=*/{});
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_EQ(error, "Ambiguous ConfigKey");
+    dbutils::deleteDb(key2);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestUnknownConfigPackage) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/"unknown.config.package",
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_EQ(error, "No configs found matching the config key");
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestUnknownDelegatePackage) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid + 1);
+
+    EXPECT_EQ(error, "No matching configs for restricted metrics delegate");
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestUnsupportedDatabaseVersion) {
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/INT_MAX,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_THAT(error, StartsWith("Unsupported sqlite version"));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestInvalidQuery) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM invalid_metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_THAT(error, StartsWith("failed to query db"));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestEnforceTtlRemovesOldEvents) {
+    int64_t currentWallTimeNs = getWallClockNs();
+    // 8 days are used here because the TTL threshold is 7 days.
+    int64_t eightDaysAgo = currentWallTimeNs - 8 * 24 * 3600 * NS_PER_SEC;
+    int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+    event1->setLogdWallClockTimestampNs(eightDaysAgo);
+
+    // Send log events to StatsLogProcessor.
+    processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+    processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST, originalEventElapsedTime + 20 * NS_PER_SEC,
+                               getWallClockNs());
+    processor->EnforceDataTtls(currentWallTimeNs, originalEventElapsedTime + 100);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    ASSERT_EQ(rows.size(), 0);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestConfigRemovalDeletesData) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+    // Query to make sure data is flushed
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+
+    processor->OnConfigRemoved(configKey);
+
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+
+    EXPECT_THAT(err, StartsWith("unable to open database file"));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestConfigRemovalDeletesDataWithoutFlush) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+    processor->OnConfigRemoved(configKey);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+
+    EXPECT_THAT(err, StartsWith("unable to open database file"));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestConfigUpdateRestrictedDelegateCleared) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Update the existing config with no delegate
+    config.clear_restricted_metrics_delegate_package_name();
+    processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    EXPECT_EQ(rows.size(), 0);
+    EXPECT_THAT(err, StartsWith("unable to open database file"));
+    dbutils::deleteDb(configKey);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestNonModularConfigUpdateRestrictedDelegate) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Update the existing config without modular update
+    processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config, false);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    EXPECT_EQ(rows.size(), 0);
+    EXPECT_THAT(err, StartsWith("no such table"));
+    dbutils::deleteDb(configKey);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestModularConfigUpdateNewRestrictedDelegate) {
+    config.clear_restricted_metrics_delegate_package_name();
+    // Update the existing config without a restricted delegate
+    processor->OnConfigUpdated(configAddedTimeNs + 10, configKey, config);
+
+    // Update the existing config with a new restricted delegate
+    config.set_restricted_metrics_delegate_package_name("new.delegate.package");
+    processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 2 * NS_PER_SEC));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    uint64_t dumpTimeNs = configAddedTimeNs + 100 * NS_PER_SEC;
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(configKey, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    ASSERT_EQ(reports.reports_size(), 0);
+
+    //  Assert the config update was not modular and a RestrictedEventMetricProducer was created.
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    EXPECT_EQ(rows.size(), 1);
+    EXPECT_THAT(rows[0],
+                ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 2 * NS_PER_SEC),
+                            _,  // wallClockNs
+                            _   // field_1
+                            ));
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestModularConfigUpdateChangeRestrictedDelegate) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Update the existing config with a new restricted delegate
+    int32_t newDelegateUid = delegate_uid + 1;
+    config.set_restricted_metrics_delegate_package_name("new.delegate.package");
+    uidMap->updateApp(configAddedTimeNs, "new.delegate.package",
+                      /*uid=*/newDelegateUid, /*versionCode=*/1,
+                      /*versionString=*/"v2",
+                      /*installer=*/"", /*certificateHash=*/{});
+    processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/newDelegateUid);
+
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(configAddedTimeNs + 100),
+                                             _,  // wallClockNs
+                                             _   // field_1
+                                             ));
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestInvalidConfigUpdateRestrictedDelegate) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    EventMetric metricWithoutMatcher = createEventMetric("metricWithoutMatcher", 999999, nullopt);
+    *config.add_event_metric() = metricWithoutMatcher;
+    // Update the existing config with an invalid config update
+    processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    EXPECT_EQ(rows.size(), 0);
+    EXPECT_THAT(err, StartsWith("unable to open database file"));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestRestrictedConfigUpdateDoesNotUpdateUidMap) {
+    auto& configKeyMap = processor->getUidMap()->mLastUpdatePerConfigKey;
+    EXPECT_EQ(configKeyMap.find(configKey), configKeyMap.end());
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestRestrictedConfigUpdateAddsDelegateRemovesUidMapEntry) {
+    auto& configKeyMap = processor->getUidMap()->mLastUpdatePerConfigKey;
+    config.clear_restricted_metrics_delegate_package_name();
+    // Update the existing config without a restricted delegate
+    processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+    EXPECT_NE(configKeyMap.find(configKey), configKeyMap.end());
+    // Update the existing config with a new restricted delegate
+    config.set_restricted_metrics_delegate_package_name(delegate_package_name.c_str());
+    processor->OnConfigUpdated(configAddedTimeNs + 1 * NS_PER_SEC, configKey, config);
+    EXPECT_EQ(configKeyMap.find(configKey), configKeyMap.end());
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestLogEventsEnforceTtls) {
+    int64_t currentWallTimeNs = getWallClockNs();
+    int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+    // 2 hours used here because the TTL check period is 1 hour.
+    int64_t newEventElapsedTime = configAddedTimeNs + 2 * 3600 * NS_PER_SEC + 1;  // 2 hrs later
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+    event1->setLogdWallClockTimestampNs(eightDaysAgo);
+    std::unique_ptr<LogEvent> event2 =
+            CreateRestrictedLogEvent(atomTag, originalEventElapsedTime + 100);
+    event2->setLogdWallClockTimestampNs(oneDayAgo);
+    std::unique_ptr<LogEvent> event3 = CreateRestrictedLogEvent(atomTag, newEventElapsedTime);
+    event3->setLogdWallClockTimestampNs(currentWallTimeNs);
+
+    processor->mLastTtlTime = originalEventElapsedTime;
+    // Send log events to StatsLogProcessor.
+    processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+    processor->OnLogEvent(event2.get(), newEventElapsedTime);
+    processor->OnLogEvent(event3.get(), newEventElapsedTime + 100);
+    processor->flushRestrictedDataLocked(newEventElapsedTime);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    ASSERT_EQ(rows.size(), 2);
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+    EXPECT_THAT(rows[0], ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime + 100),
+                                     to_string(oneDayAgo), _));
+    EXPECT_THAT(rows[1], ElementsAre(to_string(atomTag), to_string(newEventElapsedTime),
+                                     to_string(currentWallTimeNs), _));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestLogEventsDoesNotEnforceTtls) {
+    int64_t currentWallTimeNs = getWallClockNs();
+    int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+    // 30 min used here because the TTL check period is 1 hour.
+    int64_t newEventElapsedTime = configAddedTimeNs + (3600 * NS_PER_SEC) / 2;  // 30 min later
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+    event1->setLogdWallClockTimestampNs(eightDaysAgo);
+    std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(atomTag, newEventElapsedTime);
+    event2->setLogdWallClockTimestampNs(currentWallTimeNs);
+
+    processor->mLastTtlTime = originalEventElapsedTime;
+    // Send log events to StatsLogProcessor.
+    processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+    processor->OnLogEvent(event2.get(), newEventElapsedTime);
+    processor->flushRestrictedDataLocked(newEventElapsedTime);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    ASSERT_EQ(rows.size(), 2);
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+    EXPECT_THAT(rows[0], ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+                                     to_string(eightDaysAgo), _));
+    EXPECT_THAT(rows[1], ElementsAre(to_string(atomTag), to_string(newEventElapsedTime),
+                                     to_string(currentWallTimeNs), _));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestQueryEnforceTtls) {
+    int64_t currentWallTimeNs = getWallClockNs();
+    int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+    // 30 min used here because the TTL check period is 1 hour.
+    int64_t newEventElapsedTime = configAddedTimeNs + (3600 * NS_PER_SEC) / 2;  // 30 min later
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+    event1->setLogdWallClockTimestampNs(eightDaysAgo);
+    std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(atomTag, newEventElapsedTime);
+    event2->setLogdWallClockTimestampNs(currentWallTimeNs);
+
+    processor->mLastTtlTime = originalEventElapsedTime;
+    // Send log events to StatsLogProcessor.
+    processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+    processor->OnLogEvent(event2.get(), newEventElapsedTime);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult, ElementsAre(to_string(atomTag), to_string(newEventElapsedTime),
+                                             to_string(currentWallTimeNs),
+                                             _  // field_1
+                                             ));
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestNotFlushed) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get(), event->GetElapsedTimestampNs());
+    }
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_FALSE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    EXPECT_EQ(rows.size(), 0);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestEnforceDbGuardrails) {
+    int64_t currentWallTimeNs = getWallClockNs();
+    int64_t originalEventElapsedTime =
+            configAddedTimeNs + (3600 * NS_PER_SEC) * 2;  // 2 hours after boot
+    // 2 hours used here because the TTL check period is 1 hour.
+    int64_t dbEnforcementTimeNs =
+            configAddedTimeNs + (3600 * NS_PER_SEC) * 4;  // 4 hours after boot
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+    event1->setLogdWallClockTimestampNs(currentWallTimeNs);
+    // Send log events to StatsLogProcessor.
+    processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+
+    EXPECT_TRUE(StorageManager::hasFile(
+            base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult,
+                ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+                            to_string(currentWallTimeNs),
+                            _  // field_1
+                            ));
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+
+    processor->enforceDbGuardrailsIfNecessaryLocked(oneMonthLater, dbEnforcementTimeNs);
+
+    EXPECT_FALSE(StorageManager::hasFile(
+            base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestEnforceDbGuardrailsDoesNotDeleteBeforeGuardrail) {
+    int64_t currentWallTimeNs = getWallClockNs();
+    int64_t originalEventElapsedTime =
+            configAddedTimeNs + (3600 * NS_PER_SEC) * 2;  // 2 hours after boot
+    // 2 hours used here because the TTL check period is 1 hour.
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+    event1->setLogdWallClockTimestampNs(currentWallTimeNs);
+    // Send log events to StatsLogProcessor.
+    processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+
+    EXPECT_TRUE(StorageManager::hasFile(
+            base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult,
+                ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+                            to_string(currentWallTimeNs),
+                            _  // field_1
+                            ));
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+
+    processor->enforceDbGuardrailsIfNecessaryLocked(oneMonthLater, originalEventElapsedTime);
+
+    EXPECT_TRUE(StorageManager::hasFile(
+            base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestFlushInWriteDataToDisk) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get(), event->GetElapsedTimestampNs());
+    }
+
+    // Call WriteDataToDisk after 20 second because cooldown period is 15 second.
+    processor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST, 20 * NS_PER_SEC, getWallClockNs());
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    EXPECT_EQ(rows.size(), 1);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestFlushPeriodically) {
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    events.push_back(CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100));
+    events.push_back(CreateRestrictedLogEvent(
+            atomTag, configAddedTimeNs + StatsdStats::kMinFlushRestrictedPeriodNs + 1));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get(), event->GetElapsedTimestampNs());
+    }
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    // Only first event is flushed when second event is logged.
+    EXPECT_EQ(rows.size(), 1);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestOnLogEventMalformedDbNameDeleted) {
+    vector<string> emptyData;
+    string fileName = StringPrintf("%s/malformedname.db", STATS_RESTRICTED_DATA_DIR);
+    StorageManager::writeFile(fileName.c_str(), emptyData.data(), emptyData.size());
+    EXPECT_TRUE(StorageManager::hasFile(fileName.c_str()));
+    int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+    // 2 hours used here because the TTL check period is 1 hour.
+    int64_t newEventElapsedTime = configAddedTimeNs + 2 * 3600 * NS_PER_SEC + 1;  // 2 hrs later
+    std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(atomTag, newEventElapsedTime);
+    event2->setLogdWallClockTimestampNs(getWallClockNs());
+
+    processor->mLastTtlTime = originalEventElapsedTime;
+    // Send log events to StatsLogProcessor.
+    processor->OnLogEvent(event2.get(), newEventElapsedTime);
+
+    EXPECT_FALSE(StorageManager::hasFile(fileName.c_str()));
+    StorageManager::deleteFile(fileName.c_str());
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestRestrictedMetricSavesTtlToDisk) {
+    metadata::StatsMetadataList result;
+    processor->WriteMetadataToProto(getWallClockNs(), configAddedTimeNs, &result);
+
+    ASSERT_EQ(result.stats_metadata_size(), 1);
+    metadata::StatsMetadata statsMetadata = result.stats_metadata(0);
+    EXPECT_EQ(statsMetadata.config_key().config_id(), configId);
+    EXPECT_EQ(statsMetadata.config_key().uid(), config_app_uid);
+
+    ASSERT_EQ(statsMetadata.metric_metadata_size(), 1);
+    metadata::MetricMetadata metricMetadata = statsMetadata.metric_metadata(0);
+    EXPECT_EQ(metricMetadata.metric_id(), restrictedMetricId);
+    EXPECT_EQ(metricMetadata.restricted_category(), CATEGORY_UNKNOWN);
+    result.Clear();
+
+    std::unique_ptr<LogEvent> event = CreateRestrictedLogEvent(atomTag, configAddedTimeNs + 100);
+    processor->OnLogEvent(event.get());
+    processor->WriteMetadataToProto(getWallClockNs(), configAddedTimeNs, &result);
+
+    ASSERT_EQ(result.stats_metadata_size(), 1);
+    statsMetadata = result.stats_metadata(0);
+    EXPECT_EQ(statsMetadata.config_key().config_id(), configId);
+    EXPECT_EQ(statsMetadata.config_key().uid(), config_app_uid);
+
+    ASSERT_EQ(statsMetadata.metric_metadata_size(), 1);
+    metricMetadata = statsMetadata.metric_metadata(0);
+    EXPECT_EQ(metricMetadata.metric_id(), restrictedMetricId);
+    EXPECT_EQ(metricMetadata.restricted_category(), CATEGORY_DIAGNOSTIC);
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestRestrictedMetricLoadsTtlFromDisk) {
+    int64_t currentWallTimeNs = getWallClockNs();
+    int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(atomTag, originalEventElapsedTime);
+    event1->setLogdWallClockTimestampNs(eightDaysAgo);
+    processor->OnLogEvent(event1.get(), originalEventElapsedTime);
+    processor->flushRestrictedDataLocked(originalEventElapsedTime);
+    int64_t wallClockNs = 1584991200 * NS_PER_SEC;  // random time
+    int64_t metadataWriteTime = originalEventElapsedTime + 5000 * NS_PER_SEC;
+    processor->SaveMetadataToDisk(wallClockNs, metadataWriteTime);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+    EXPECT_THAT(rows[0], ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+                                     to_string(eightDaysAgo), _));
+
+    auto processor2 =
+            CreateStatsLogProcessor(/*baseTimeNs=*/0, configAddedTimeNs, config, configKey,
+                                    /*puller=*/nullptr, /*atomTag=*/0, uidMap);
+    // 2 hours used here because the TTL check period is 1 hour.
+    int64_t newEventElapsedTime = configAddedTimeNs + 2 * 3600 * NS_PER_SEC + 1;  // 2 hrs later
+    processor2->LoadMetadataFromDisk(wallClockNs, newEventElapsedTime);
+
+    // Log another event and check that the original TTL is maintained across reboot
+    std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(atomTag, newEventElapsedTime);
+    event2->setLogdWallClockTimestampNs(currentWallTimeNs);
+    processor2->OnLogEvent(event2.get(), newEventElapsedTime);
+    processor2->flushRestrictedDataLocked(newEventElapsedTime);
+
+    columnTypes.clear();
+    columnNames.clear();
+    rows.clear();
+    EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+    EXPECT_THAT(rows[0], ElementsAre(to_string(atomTag), to_string(newEventElapsedTime),
+                                     to_string(currentWallTimeNs), _));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestNewRestrictionCategoryEventDeletesTable) {
+    int64_t currentWallTimeNs = getWallClockNs();
+    int64_t originalEventElapsedTime = configAddedTimeNs + 100;
+    std::unique_ptr<LogEvent> event1 =
+            CreateNonRestrictedLogEvent(atomTag, originalEventElapsedTime);
+    processor->OnLogEvent(event1.get());
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << dbutils::reformatMetricId(restrictedMetricId);
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult,
+                ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime),
+                            _,  // wallTimestampNs
+                            _   // field_1
+                            ));
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+
+    // Log a second event that will go into the cache
+    std::unique_ptr<LogEvent> event2 =
+            CreateNonRestrictedLogEvent(atomTag, originalEventElapsedTime + 100);
+    processor->OnLogEvent(event2.get());
+
+    // Log a third event with a different category
+    std::unique_ptr<LogEvent> event3 =
+            CreateRestrictedLogEvent(atomTag, originalEventElapsedTime + 200);
+    processor->OnLogEvent(event3.get());
+
+    processor->querySql(query.str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult,
+                ElementsAre(to_string(atomTag), to_string(originalEventElapsedTime + 200),
+                            _,  // wallTimestampNs
+                            _   // field_1
+                            ));
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER));
+}
+
+TEST_F(RestrictedEventMetricE2eTest, TestDeviceInfoTableCreated) {
+    std::string query = "SELECT * FROM device_info";
+    processor->querySql(query.c_str(), /*minSqlClientVersion=*/0,
+                        /*policyConfig=*/{}, mockStatsQueryCallback,
+                        /*configKey=*/configId, /*configPackage=*/config_package_name,
+                        /*callingUid=*/delegate_uid);
+    EXPECT_EQ(rowCountResult, 1);
+    EXPECT_THAT(queryDataResult, ElementsAre(_, _, _, _, _, _, _, _, _, _));
+    EXPECT_THAT(columnNamesResult,
+                ElementsAre("sdkVersion", "model", "product", "hardware", "device", "osBuild",
+                            "fingerprint", "brand", "manufacturer", "board"));
+    EXPECT_THAT(columnTypesResult,
+                ElementsAre(SQLITE_INTEGER, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT,
+                            SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT));
+}
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/tests/e2e/StringReplace_e2e_test.cpp b/statsd/tests/e2e/StringReplace_e2e_test.cpp
new file mode 100644
index 0000000..d92499d
--- /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"([0-9]+$)");  // 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"([0-9]+$)");  // 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"([0-9]+$)");  // 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/e2e/ValueMetric_pull_e2e_test.cpp b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index 1d8307b..323ed17 100644
--- a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -35,7 +35,6 @@
 
 StatsdConfig CreateStatsdConfig(bool useCondition = true) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
     auto pulledAtomMatcher =
             CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE);
@@ -67,7 +66,6 @@
 
 StatsdConfig CreateStatsdConfigWithStates() {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");     // LogEvent defaults to UID of root.
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
 
     auto pulledAtomMatcher = CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE);
@@ -276,6 +274,7 @@
     ASSERT_EQ(1, reports.reports_size());
     ASSERT_EQ(1, reports.reports(0).metrics_size());
     StatsLogReport::ValueMetricDataWrapper valueMetrics;
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_estimated_data_bytes());
     sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).value_metrics(), &valueMetrics);
     ASSERT_GT((int)valueMetrics.data_size(), 1);
 
@@ -652,7 +651,6 @@
 TEST(ValueMetricE2eTest, TestInitWithSlicedState) {
     // Create config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto pulledAtomMatcher =
             CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE);
@@ -706,7 +704,6 @@
 TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions) {
     // Create config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto cpuTimePerUidMatcher =
             CreateSimpleAtomMatcher("CpuTimePerUidMatcher", util::CPU_TIME_PER_UID);
@@ -766,7 +763,6 @@
 TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions) {
     // Create config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     auto cpuTimePerUidMatcher =
             CreateSimpleAtomMatcher("CpuTimePerUidMatcher", util::CPU_TIME_PER_UID);
@@ -809,7 +805,6 @@
 TEST(ValueMetricE2eTest, TestInitWithValueFieldPositionALL) {
     // Create config.
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     AtomMatcher testAtomReportedMatcher =
             CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
@@ -836,6 +831,76 @@
     ASSERT_EQ(0, processor->mMetricsManagers.size());
 }
 
+TEST(ValueMetricE2eTest, TestInitWithMultipleAggTypes) {
+    // Create config.
+    StatsdConfig config;
+
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    // Create value metric.
+    int64_t metricId = 123456;
+    ValueMetric* valueMetric = config.add_value_metric();
+    valueMetric->set_id(metricId);
+    valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    valueMetric->set_what(testAtomReportedMatcher.id());
+    *valueMetric->mutable_value_field() = CreateDimensions(
+            util::TEST_ATOM_REPORTED, {2 /*int_field*/, 2 /*int_field*/, 3 /*long_field*/,
+                                       3 /*long_field*/, 3 /*long_field*/});
+    valueMetric->add_aggregation_types(ValueMetric::SUM);
+    valueMetric->add_aggregation_types(ValueMetric::MIN);
+    valueMetric->add_aggregation_types(ValueMetric::MAX);
+    valueMetric->add_aggregation_types(ValueMetric::AVG);
+    valueMetric->add_aggregation_types(ValueMetric::MIN);
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    ASSERT_EQ(1, processor->mMetricsManagers.size());
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(1, metricsManager->mAllMetricProducers.size());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+}
+
+TEST(ValueMetricE2eTest, TestInitWithDefaultAggType) {
+    // Create config.
+    StatsdConfig config;
+
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    // Create value metric.
+    int64_t metricId = 123456;
+    ValueMetric* valueMetric = config.add_value_metric();
+    valueMetric->set_id(metricId);
+    valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    valueMetric->set_what(testAtomReportedMatcher.id());
+    *valueMetric->mutable_value_field() =
+            CreateDimensions(util::TEST_ATOM_REPORTED, {3 /*long_field*/, 2 /*int_field*/});
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    ASSERT_EQ(1, processor->mMetricsManagers.size());
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    ASSERT_EQ(1, metricsManager->mAllMetricProducers.size());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
index 52bc222..1567db0 100644
--- a/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
+++ b/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -30,7 +30,6 @@
 
 StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
     *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
diff --git a/statsd/tests/external/puller_util_test.cpp b/statsd/tests/external/puller_util_test.cpp
index 25c0c7e..71f4de4 100644
--- a/statsd/tests/external/puller_util_test.cpp
+++ b/statsd/tests/external/puller_util_test.cpp
@@ -22,7 +22,6 @@
 
 #include "../metrics/metrics_test_helper.h"
 #include "FieldValue.h"
-#include "annotations.h"
 #include "stats_event.h"
 #include "tests/statsd_test_util.h"
 
diff --git a/statsd/tests/guardrail/StatsdStats_test.cpp b/statsd/tests/guardrail/StatsdStats_test.cpp
index d37a810..1d56caa 100644
--- a/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -27,13 +27,43 @@
 
 #ifdef __ANDROID__
 
+namespace std {
+void PrintTo(const tuple<int, size_t>& atomIdDimensionLimitTuple, ostream* os) {
+    *os << get<0>(atomIdDimensionLimitTuple) << "_" << get<1>(atomIdDimensionLimitTuple);
+}
+}  // namespace std
+
 namespace android {
 namespace os {
 namespace statsd {
+namespace {
 
+using namespace testing;
 using PerSubscriptionStats = StatsdStatsReport_SubscriptionStats_PerSubscriptionStats;
+using std::tuple;
 using std::vector;
 
+class StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap
+    : public TestWithParam<tuple<int, size_t>> {};
+INSTANTIATE_TEST_SUITE_P(StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap,
+                         StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap,
+                         Combine(Values(10022 /* BINDER_CALLS */, 10024 /* LOOPER_STATS */,
+                                        10010 /* CPU_TIME_PER_UID_FREQ */),
+                                 Values(-1, 0, 500, 800, 1000, 3000, 3300)),
+                         PrintToStringParamName());
+
+class StatsdStatsTest_GetAtomDimensionKeySizeLimit_NotInMap
+    : public StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap {};
+
+INSTANTIATE_TEST_SUITE_P(StatsdStatsTest_GetAtomDimensionKeySizeLimit_NotInMap,
+                         StatsdStatsTest_GetAtomDimensionKeySizeLimit_NotInMap,
+                         Combine(Values(util::TEST_ATOM_REPORTED, util::SCREEN_STATE_CHANGED,
+                                        util::SUBSYSTEM_SLEEP_STATE),
+                                 Values(-1, 0, 500, 800, 1000, 3000, 3300)),
+                         PrintToStringParamName());
+
+}  // anonymous namespace
+
 TEST(StatsdStatsTest, TestValidConfigAdd) {
     StatsdStats stats;
     ConfigKey key(0, 12345);
@@ -218,9 +248,9 @@
     stats.noteDataDropped(key, 123);
 
     // dump report -> 3
-    stats.noteMetricsReportSent(key, 0);
-    stats.noteMetricsReportSent(key, 0);
-    stats.noteMetricsReportSent(key, 0);
+    stats.noteMetricsReportSent(key, 0, 1);
+    stats.noteMetricsReportSent(key, 0, 2);
+    stats.noteMetricsReportSent(key, 0, 3);
 
     // activation_time_sec -> 2
     stats.noteActiveStatusChanged(key, true);
@@ -238,6 +268,10 @@
     EXPECT_EQ(123, configReport.data_drop_bytes(0));
     ASSERT_EQ(3, configReport.dump_report_time_sec_size());
     ASSERT_EQ(3, configReport.dump_report_data_size_size());
+    ASSERT_EQ(3, configReport.dump_report_number_size());
+    EXPECT_EQ(1, configReport.dump_report_number(0));
+    EXPECT_EQ(2, configReport.dump_report_number(1));
+    EXPECT_EQ(3, configReport.dump_report_number(2));
     ASSERT_EQ(2, configReport.activation_time_sec_size());
     ASSERT_EQ(1, configReport.deactivation_time_sec_size());
     ASSERT_EQ(1, configReport.annotation_size());
@@ -439,6 +473,98 @@
     EXPECT_EQ(1L, atomStats2.max_bucket_boundary_delay_ns());
 }
 
+TEST(StatsdStatsTest, TestRestrictedMetricsStats) {
+    StatsdStats stats;
+    const int64_t metricId = -1234556L;
+    ConfigKey key(0, 12345);
+    stats.noteConfigReceived(key, 2, 3, 4, 5, {}, nullopt);
+    stats.noteRestrictedMetricInsertError(key, metricId);
+    stats.noteRestrictedMetricTableCreationError(key, metricId);
+    stats.noteRestrictedMetricTableDeletionError(key, metricId);
+    stats.noteDeviceInfoTableCreationFailed(key);
+    stats.noteRestrictedMetricFlushLatency(key, metricId, 3000);
+    stats.noteRestrictedMetricFlushLatency(key, metricId, 3001);
+    stats.noteRestrictedMetricCategoryChanged(key, metricId);
+    stats.noteRestrictedConfigFlushLatency(key, 4000);
+    ConfigKey configKeyWithoutError(0, 666);
+    stats.noteConfigReceived(configKeyWithoutError, 2, 3, 4, 5, {}, nullopt);
+    stats.noteDbCorrupted(key);
+    stats.noteDbCorrupted(key);
+    stats.noteDbSizeExceeded(key);
+    stats.noteDbStatFailed(key);
+    stats.noteDbConfigInvalid(key);
+    stats.noteDbTooOld(key);
+    stats.noteDbDeletionConfigRemoved(key);
+    stats.noteDbDeletionConfigUpdated(key);
+    stats.noteRestrictedConfigDbSize(key, 999, 111);
+
+    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
+    ASSERT_EQ(2, report.config_stats().size());
+    ASSERT_EQ(0, report.config_stats(0).restricted_metric_stats().size());
+    ASSERT_EQ(1, report.config_stats(1).restricted_metric_stats().size());
+    EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).insert_error());
+    EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).table_creation_error());
+    EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).table_deletion_error());
+    EXPECT_EQ(1, report.config_stats(1).restricted_metric_stats(0).category_changed_count());
+    ASSERT_EQ(2, report.config_stats(1).restricted_metric_stats(0).flush_latency_ns().size());
+    EXPECT_EQ(3000, report.config_stats(1).restricted_metric_stats(0).flush_latency_ns(0));
+    EXPECT_EQ(3001, report.config_stats(1).restricted_metric_stats(0).flush_latency_ns(1));
+    ASSERT_EQ(1, report.config_stats(1).restricted_db_size_time_sec().size());
+    EXPECT_EQ(999, report.config_stats(1).restricted_db_size_time_sec(0));
+    ASSERT_EQ(1, report.config_stats(1).restricted_db_size_bytes().size());
+    EXPECT_EQ(111, report.config_stats(1).restricted_db_size_bytes(0));
+    ASSERT_EQ(1, report.config_stats(1).restricted_flush_latency().size());
+    EXPECT_EQ(4000, report.config_stats(1).restricted_flush_latency(0));
+    EXPECT_TRUE(report.config_stats(1).device_info_table_creation_failed());
+    EXPECT_EQ(metricId, report.config_stats(1).restricted_metric_stats(0).restricted_metric_id());
+    EXPECT_EQ(2, report.config_stats(1).restricted_db_corrupted_count());
+    EXPECT_EQ(1, report.config_stats(1).db_deletion_stat_failed());
+    EXPECT_EQ(1, report.config_stats(1).db_deletion_size_exceeded_limit());
+    EXPECT_EQ(1, report.config_stats(1).db_deletion_config_invalid());
+    EXPECT_EQ(1, report.config_stats(1).db_deletion_too_old());
+    EXPECT_EQ(1, report.config_stats(1).db_deletion_config_removed());
+    EXPECT_EQ(1, report.config_stats(1).db_deletion_config_updated());
+}
+
+TEST(StatsdStatsTest, TestRestrictedMetricsQueryStats) {
+    StatsdStats stats;
+    const int32_t callingUid = 100;
+    ConfigKey configKey(0, 12345);
+    const string configPackage = "com.google.android.gm";
+    int64_t beforeNoteMetricSucceed = getWallClockNs();
+    stats.noteQueryRestrictedMetricSucceed(configKey.GetId(), configPackage, configKey.GetUid(),
+                                           callingUid, /*queryLatencyNs=*/5 * NS_PER_SEC);
+    int64_t afterNoteMetricSucceed = getWallClockNs();
+
+    const int64_t configIdWithError = 111;
+    stats.noteQueryRestrictedMetricFailed(configIdWithError, configPackage, std::nullopt,
+                                          callingUid, InvalidQueryReason(AMBIGUOUS_CONFIG_KEY));
+    stats.noteQueryRestrictedMetricFailed(configIdWithError, configPackage, std::nullopt,
+                                          callingUid, InvalidQueryReason(AMBIGUOUS_CONFIG_KEY),
+                                          "error_message");
+
+    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
+    ASSERT_EQ(3, report.restricted_metric_query_stats().size());
+    EXPECT_EQ(configKey.GetId(), report.restricted_metric_query_stats(0).config_id());
+    EXPECT_EQ(configKey.GetUid(), report.restricted_metric_query_stats(0).config_uid());
+    EXPECT_EQ(callingUid, report.restricted_metric_query_stats(0).calling_uid());
+    EXPECT_EQ(configPackage, report.restricted_metric_query_stats(0).config_package());
+    EXPECT_FALSE(report.restricted_metric_query_stats(0).has_query_error());
+    EXPECT_LT(beforeNoteMetricSucceed,
+              report.restricted_metric_query_stats(0).query_wall_time_ns());
+    EXPECT_GT(afterNoteMetricSucceed, report.restricted_metric_query_stats(0).query_wall_time_ns());
+    EXPECT_EQ(5 * NS_PER_SEC, report.restricted_metric_query_stats(0).query_latency_ns());
+    EXPECT_EQ(configIdWithError, report.restricted_metric_query_stats(1).config_id());
+    EXPECT_EQ(AMBIGUOUS_CONFIG_KEY, report.restricted_metric_query_stats(1).invalid_query_reason());
+    EXPECT_EQ(false, report.restricted_metric_query_stats(1).has_config_uid());
+    EXPECT_FALSE(report.restricted_metric_query_stats(1).has_query_error());
+    EXPECT_FALSE(report.restricted_metric_query_stats(1).has_query_latency_ns());
+    EXPECT_EQ("error_message", report.restricted_metric_query_stats(2).query_error());
+    EXPECT_FALSE(report.restricted_metric_query_stats(2).has_query_latency_ns());
+    EXPECT_NE(report.restricted_metric_query_stats(1).query_wall_time_ns(),
+              report.restricted_metric_query_stats(0).query_wall_time_ns());
+}
+
 TEST(StatsdStatsTest, TestAnomalyMonitor) {
     StatsdStats stats;
     stats.noteRegisteredAnomalyAlarmChanged();
@@ -460,7 +586,7 @@
     for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
         stats.noteDataDropped(key, timestamps[i]);
         stats.noteBroadcastSent(key, timestamps[i]);
-        stats.noteMetricsReportSent(key, 0, timestamps[i]);
+        stats.noteMetricsReportSent(key, 0, timestamps[i], i + 1);
         stats.noteActiveStatusChanged(key, true, timestamps[i]);
         stats.noteActiveStatusChanged(key, false, timestamps[i]);
     }
@@ -470,7 +596,7 @@
     // now it should trigger removing oldest timestamp
     stats.noteDataDropped(key, 123, 10000);
     stats.noteBroadcastSent(key, 10000);
-    stats.noteMetricsReportSent(key, 0, 10000);
+    stats.noteMetricsReportSent(key, 0, 10000, 21);
     stats.noteActiveStatusChanged(key, true, 10000);
     stats.noteActiveStatusChanged(key, false, 10000);
 
@@ -487,7 +613,7 @@
     // the oldest timestamp is the second timestamp in history
     EXPECT_EQ(1, configStats->broadcast_sent_time_sec.front());
     EXPECT_EQ(1, configStats->data_drop_bytes.front());
-    EXPECT_EQ(1, configStats->dump_report_stats.front().first);
+    EXPECT_EQ(1, configStats->dump_report_stats.front().mDumpReportTimeSec);
     EXPECT_EQ(1, configStats->activation_time_sec.front());
     EXPECT_EQ(1, configStats->deactivation_time_sec.front());
 
@@ -495,7 +621,7 @@
     EXPECT_EQ(newTimestamp, configStats->broadcast_sent_time_sec.back());
     EXPECT_EQ(newTimestamp, configStats->data_drop_time_sec.back());
     EXPECT_EQ(123, configStats->data_drop_bytes.back());
-    EXPECT_EQ(newTimestamp, configStats->dump_report_stats.back().first);
+    EXPECT_EQ(newTimestamp, configStats->dump_report_stats.back().mDumpReportTimeSec);
     EXPECT_EQ(newTimestamp, configStats->activation_time_sec.back());
     EXPECT_EQ(newTimestamp, configStats->deactivation_time_sec.back());
 }
@@ -624,6 +750,16 @@
     EXPECT_FALSE(nonPlatformPushedAtomStats.has_skip_count());
 }
 
+TEST(StatsdStatsTest, TestQueueStats) {
+    StatsdStats stats;
+
+    stats.noteEventQueueSize(100, 1000);
+    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ true);
+
+    ASSERT_EQ(100, report.event_queue_stats().max_size_observed());
+    ASSERT_EQ(1000, report.event_queue_stats().max_size_observed_elapsed_nanos());
+}
+
 TEST(StatsdStatsTest, TestAtomLoggedAndDroppedStats) {
     StatsdStats stats;
 
@@ -872,6 +1008,111 @@
                 Contains(Property(&PerSubscriptionStats::id, Eq(maxSubs + 1))));
 }
 
+TEST(StatsdStatsTest, TestEnforceDimensionKeySizeLimit) {
+    EXPECT_EQ(StatsdStats::clampDimensionKeySizeLimit(-1),
+              StatsdStats::kDimensionKeySizeHardLimitMin);
+    EXPECT_EQ(StatsdStats::clampDimensionKeySizeLimit(0),
+              StatsdStats::kDimensionKeySizeHardLimitMin);
+    EXPECT_EQ(StatsdStats::clampDimensionKeySizeLimit(500),
+              StatsdStats::kDimensionKeySizeHardLimitMin);
+    EXPECT_EQ(StatsdStats::clampDimensionKeySizeLimit(1000), 1000);
+    EXPECT_EQ(StatsdStats::clampDimensionKeySizeLimit(3500),
+              StatsdStats::kDimensionKeySizeHardLimitMax);
+}
+
+TEST(StatsdStatsTest, TestSocketLossStats) {
+    StatsdStats stats;
+
+    const int maxLossEvents = StatsdStats::kMaxSocketLossStatsSize;
+
+    // Note maxLossEvents + 1
+    for (int eventId = 0; eventId <= maxLossEvents; eventId++) {
+        SocketLossInfo info;
+
+        info.uid = eventId;
+        info.firstLossTsNanos = 10 * eventId;
+        info.lastLossTsNanos = 10 * eventId + 1;
+
+        info.atomIds.push_back(eventId * 10);
+        info.errors.push_back(eventId * 20);
+        info.counts.push_back(eventId * 30);
+
+        stats.noteAtomSocketLoss(info);
+    }
+
+    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
+
+    auto socketLossStats = report.socket_loss_stats();
+    ASSERT_EQ(socketLossStats.loss_stats_per_uid().size(), maxLossEvents);
+
+    for (int i = 0; i < socketLossStats.loss_stats_per_uid().size(); i++) {
+        const auto& info = report.socket_loss_stats().loss_stats_per_uid(i);
+
+        // due to the very first one with id 0 is popped out from the list ids (index) start from 1
+        const int index = i + 1;
+
+        ASSERT_EQ(info.uid(), index);
+        ASSERT_EQ(info.first_timestamp_nanos(), 10 * index);
+        ASSERT_EQ(info.last_timestamp_nanos(), 10 * index + 1);
+
+        ASSERT_EQ(info.atom_id_loss_stats().size(), 1);
+
+        ASSERT_EQ(info.atom_id_loss_stats(0).atom_id(), index * 10);
+        ASSERT_EQ(info.atom_id_loss_stats(0).error(), index * 20);
+        ASSERT_EQ(info.atom_id_loss_stats(0).count(), index * 30);
+    }
+}
+
+TEST(StatsdStatsTest, TestSocketLossStatsOverflowCounter) {
+    StatsdStats stats;
+
+    const int uidsCount = 5;
+    const int lossEventCount = 5;
+
+    for (int uid = 0; uid < uidsCount; uid++) {
+        for (int eventId = 0; eventId < lossEventCount; eventId++) {
+            SocketLossInfo info;
+
+            info.uid = uid;
+            info.firstLossTsNanos = 10 * eventId;
+            info.lastLossTsNanos = 10 * eventId + 1;
+            // the counter value will be accumulated
+            info.overflowCounter = 1;
+
+            info.atomIds.push_back(eventId * 10);
+            info.errors.push_back(eventId * 20);
+            info.counts.push_back(eventId * 30);
+
+            stats.noteAtomSocketLoss(info);
+        }
+    }
+    StatsdStatsReport report = getStatsdStatsReport(stats, /* reset stats */ false);
+
+    auto socketLossStatsOverflowCounters =
+            report.socket_loss_stats().loss_stats_overflow_counters();
+    ASSERT_EQ(socketLossStatsOverflowCounters.size(), uidsCount);
+
+    for (int i = 0; i < socketLossStatsOverflowCounters.size(); i++) {
+        const auto& counters = report.socket_loss_stats().loss_stats_overflow_counters(i);
+
+        ASSERT_EQ(counters.uid(), i);
+        ASSERT_EQ(counters.count(), lossEventCount);
+    }
+}
+
+TEST_P(StatsdStatsTest_GetAtomDimensionKeySizeLimit_InMap, TestGetAtomDimensionKeySizeLimits) {
+    const auto& [atomId, defaultHardLimit] = GetParam();
+    EXPECT_EQ(StatsdStats::getAtomDimensionKeySizeLimits(atomId, defaultHardLimit),
+              StatsdStats::kAtomDimensionKeySizeLimitMap.at(atomId));
+}
+
+TEST_P(StatsdStatsTest_GetAtomDimensionKeySizeLimit_NotInMap, TestGetAtomDimensionKeySizeLimits) {
+    const auto& [atomId, defaultHardLimit] = GetParam();
+    EXPECT_EQ(
+            StatsdStats::getAtomDimensionKeySizeLimits(atomId, defaultHardLimit),
+            (std::pair<size_t, size_t>(StatsdStats::kDimensionKeySizeSoftLimit, defaultHardLimit)));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/indexed_priority_queue_test.cpp b/statsd/tests/indexed_priority_queue_test.cpp
index 3a65456..1d365e8 100644
--- a/statsd/tests/indexed_priority_queue_test.cpp
+++ b/statsd/tests/indexed_priority_queue_test.cpp
@@ -30,7 +30,7 @@
     const std::string b;
 
     struct Smaller {
-        bool operator()(const sp<const AATest> a, const sp<const AATest> b) const {
+        bool operator()(const sp<const AATest>& a, const sp<const AATest>& b) const {
             return (a->val < b->val);
         }
     };
diff --git a/statsd/tests/log_event/LogEventQueue_test.cpp b/statsd/tests/log_event/LogEventQueue_test.cpp
index a15f95b..fe6a27a 100644
--- a/statsd/tests/log_event/LogEventQueue_test.cpp
+++ b/statsd/tests/log_event/LogEventQueue_test.cpp
@@ -20,6 +20,7 @@
 
 #include <thread>
 
+#include "socket/StatsSocketListener.h"
 #include "stats_event.h"
 #include "tests/statsd_test_util.h"
 
@@ -34,37 +35,43 @@
 
 namespace {
 
-std::unique_ptr<LogEvent> makeLogEvent(uint64_t timestampNs) {
+AStatsEvent* makeStatsEvent(uint64_t timestampNs) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, 10);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    AStatsEvent_build(statsEvent);
+    return statsEvent;
+}
 
+std::unique_ptr<LogEvent> makeLogEvent(uint64_t timestampNs) {
+    AStatsEvent* statsEvent = makeStatsEvent(timestampNs);
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
     parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    EXPECT_EQ(logEvent->GetElapsedTimestampNs(), timestampNs);
     return logEvent;
 }
 
-} // anonymous namespace
+}  // anonymous namespace
 
 #ifdef __ANDROID__
 TEST(LogEventQueue_test, TestGoodConsumer) {
     LogEventQueue queue(50);
-    int64_t timeBaseNs = 100;
-    std::thread writer([&queue, timeBaseNs] {
+    int64_t eventTimeNs = 100;
+    std::thread writer([&queue, eventTimeNs] {
+        LogEventQueue::Result result;
         for (int i = 0; i < 100; i++) {
-            int64_t oldestEventNs;
-            bool success = queue.push(makeLogEvent(timeBaseNs + i * 1000), &oldestEventNs);
-            EXPECT_TRUE(success);
+            result = queue.push(makeLogEvent(eventTimeNs + i * 1000));
+            EXPECT_TRUE(result.success);
             std::this_thread::sleep_for(std::chrono::milliseconds(1));
         }
     });
 
-    std::thread reader([&queue, timeBaseNs] {
+    std::thread reader([&queue, eventTimeNs] {
         for (int i = 0; i < 100; i++) {
             auto event = queue.waitPop();
             EXPECT_TRUE(event != nullptr);
             // All events are in right order.
-            EXPECT_EQ(timeBaseNs + i * 1000, event->GetElapsedTimestampNs());
+            EXPECT_EQ(eventTimeNs + i * 1000, event->GetElapsedTimestampNs());
         }
     });
 
@@ -74,13 +81,15 @@
 
 TEST(LogEventQueue_test, TestSlowConsumer) {
     LogEventQueue queue(50);
-    int64_t timeBaseNs = 100;
-    std::thread writer([&queue, timeBaseNs] {
+    int64_t eventTimeNs = 100;
+    std::thread writer([&queue, eventTimeNs] {
         int failure_count = 0;
-        int64_t oldestEventNs;
+        LogEventQueue::Result result;
         for (int i = 0; i < 100; i++) {
-            bool success = queue.push(makeLogEvent(timeBaseNs + i * 1000), &oldestEventNs);
-            if (!success) failure_count++;
+            result = queue.push(makeLogEvent(eventTimeNs + i * 1000));
+            if (!result.success) {
+                failure_count++;
+            }
             std::this_thread::sleep_for(std::chrono::milliseconds(1));
         }
 
@@ -89,16 +98,16 @@
         // There will be at least 45 events lost due to overflow.
         EXPECT_TRUE(failure_count >= 45);
         // The oldest event must be at least the 6th event.
-        EXPECT_TRUE(oldestEventNs <= (100 + 5 * 1000));
+        EXPECT_TRUE(result.oldestTimestampNs <= (100 + 5 * 1000));
     });
 
-    std::thread reader([&queue, timeBaseNs] {
+    std::thread reader([&queue, eventTimeNs] {
         // The consumer quickly processed 5 events, then it got stuck (not reading anymore).
         for (int i = 0; i < 5; i++) {
             auto event = queue.waitPop();
             EXPECT_TRUE(event != nullptr);
             // All events are in right order.
-            EXPECT_EQ(timeBaseNs + i * 1000, event->GetElapsedTimestampNs());
+            EXPECT_EQ(eventTimeNs + i * 1000, event->GetElapsedTimestampNs());
         }
     });
 
@@ -106,6 +115,87 @@
     writer.join();
 }
 
+TEST(LogEventQueue_test, TestQueueMaxSize) {
+    StatsdStats::getInstance().reset();
+
+    LogEventQueue queue(50);
+    LogEventFilter filter;
+    filter.setFilteringEnabled(false);
+
+    int64_t eventTimeNs = 100;
+    int64_t oldestEventNs = 0;
+    int32_t newSize = 0;
+    for (int i = 0; i < 30; i++, eventTimeNs++) {
+        auto statsEvent = makeStatsEvent(eventTimeNs);
+        size_t bufferSize;
+        const uint8_t* buffer = AStatsEvent_getBuffer(statsEvent, &bufferSize);
+        StatsSocketListener::processStatsEventBuffer(buffer, bufferSize, 0, 0, queue, filter);
+        AStatsEvent_release(statsEvent);
+        EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObserved, i + 1);
+        EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObservedElapsedNanos, eventTimeNs);
+    }
+
+    const int32_t lastMaxSizeObserved = StatsdStats::getInstance().mEventQueueMaxSizeObserved;
+    const int64_t lastMaxSizeElapsedNanos =
+            StatsdStats::getInstance().mEventQueueMaxSizeObservedElapsedNanos;
+
+    // consumer reads the entire queue
+    int64_t nextEventTs = 100;
+    for (int i = 0; i < 30; i++, nextEventTs++) {
+        auto event = queue.waitPop();
+        EXPECT_TRUE(event != nullptr);
+        // All events are in right order.
+        EXPECT_EQ(nextEventTs, event->GetElapsedTimestampNs());
+    }
+
+    // the expectation after queue drained entirely the max count & ts do not update for
+    // smaller values
+    {
+        auto statsEvent = makeStatsEvent(eventTimeNs);
+        size_t bufferSize;
+        const uint8_t* buffer = AStatsEvent_getBuffer(statsEvent, &bufferSize);
+        StatsSocketListener::processStatsEventBuffer(buffer, bufferSize, 0, 0, queue, filter);
+        AStatsEvent_release(statsEvent);
+        EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObserved, lastMaxSizeObserved);
+        EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObservedElapsedNanos,
+                  lastMaxSizeElapsedNanos);
+        eventTimeNs++;
+    }
+
+    for (int i = 0; i < 1; i++, nextEventTs++) {
+        auto event = queue.waitPop();
+        EXPECT_TRUE(event != nullptr);
+        // All events are in right order.
+        EXPECT_EQ(nextEventTs, event->GetElapsedTimestampNs());
+    }
+
+    // the expectation after queue drained entirely the max count & ts do update for
+    // bigger values
+    // fill up to the the previous max values observed - stats are not changed
+    for (int i = 0; i < lastMaxSizeObserved; i++, eventTimeNs++) {
+        auto statsEvent = makeStatsEvent(eventTimeNs);
+        size_t bufferSize;
+        const uint8_t* buffer = AStatsEvent_getBuffer(statsEvent, &bufferSize);
+        StatsSocketListener::processStatsEventBuffer(buffer, bufferSize, 0, 0, queue, filter);
+        AStatsEvent_release(statsEvent);
+        EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObserved, lastMaxSizeObserved);
+        EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObservedElapsedNanos,
+                  lastMaxSizeElapsedNanos);
+    }
+
+    // add extra elements to update the stats
+    for (int i = 0; i < 10; i++, eventTimeNs++) {
+        auto statsEvent = makeStatsEvent(eventTimeNs);
+        size_t bufferSize;
+        const uint8_t* buffer = AStatsEvent_getBuffer(statsEvent, &bufferSize);
+        StatsSocketListener::processStatsEventBuffer(buffer, bufferSize, 0, 0, queue, filter);
+        AStatsEvent_release(statsEvent);
+        EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObserved,
+                  lastMaxSizeObserved + i + 1);
+        EXPECT_EQ(StatsdStats::getInstance().mEventQueueMaxSizeObservedElapsedNanos, eventTimeNs);
+    }
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/metrics/CountMetricProducer_test.cpp b/statsd/tests/metrics/CountMetricProducer_test.cpp
index 32c48b0..8db5bce 100644
--- a/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -75,8 +75,10 @@
     metric.set_bucket(ONE_MINUTE);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2);
+                                      wizard, protoHash, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2,
+                                      provider);
     EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs);
     EXPECT_EQ(10, countProducer.mCurrentBucketNum);
     EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs());
@@ -94,9 +96,10 @@
     metric.set_bucket(ONE_MINUTE);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs,
+                                      provider);
 
     // 2 events in bucket 1.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -158,8 +161,9 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
-                                      protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      protoHash, bucketStartTimeNs, bucketStartTimeNs, provider);
     assertConditionTimer(countProducer.mConditionTimer, false, 0, 0);
 
     countProducer.onConditionChanged(true, bucketStartTimeNs);
@@ -229,9 +233,10 @@
 
     EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue));
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, 0 /*condition tracker index*/,
                                       {ConditionState::kUnknown}, wizard, protoHash,
-                                      bucketStartTimeNs, bucketStartTimeNs);
+                                      bucketStartTimeNs, bucketStartTimeNs, provider);
 
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     countProducer.flushIfNeededLocked(bucketStartTimeNs + 1);
@@ -268,9 +273,9 @@
     alert.set_trigger_if_sum_gt(2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard,
-                                      protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      protoHash, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     sp<AnomalyTracker> anomalyTracker =
             countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs);
@@ -337,9 +342,9 @@
     metric.set_split_bucket_for_app_upgrade(true);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard,
-                                      protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      protoHash, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     // Bucket is flushed yet.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -399,8 +404,9 @@
     alert.set_trigger_if_sum_gt(2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, {}, wizard,
-                                      protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      protoHash, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     sp<AnomalyTracker> anomalyTracker =
             countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs);
@@ -467,9 +473,10 @@
     metric.set_bucket(ONE_MINUTE);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, bucketStartTimeNs,
+                                      provider);
 
     sp<AnomalyTracker> anomalyTracker =
             countProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs);
@@ -531,8 +538,9 @@
     int64_t oneDayNs = 24 * 60 * 60 * 1e9;
     int64_t fiveWeeksNs = 5 * 7 * oneDayNs;
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     CountMetricProducer countProducer(kConfigKey, metric, -1 /* meaning no condition */, {}, wizard,
-                                      protoHash, oneDayNs, fiveWeeksNs);
+                                      protoHash, oneDayNs, fiveWeeksNs, provider);
 
     int64_t fiveWeeksOneDayNs = fiveWeeksNs + oneDayNs;
 
diff --git a/statsd/tests/metrics/DurationMetricProducer_test.cpp b/statsd/tests/metrics/DurationMetricProducer_test.cpp
index 3b24d85..5cf0221 100644
--- a/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -71,11 +71,12 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
 
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2);
+            wizard, protoHash, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2, provider);
 
     EXPECT_EQ(600500000000, durationProducer.mCurrentBucketStartTimeNs);
     EXPECT_EQ(10, durationProducer.mCurrentBucketNum);
@@ -99,11 +100,12 @@
     makeLogEvent(&event2, bucketStartTimeNs + bucketSizeNs + 2, tagId);
 
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /*no condition*/, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
@@ -142,12 +144,13 @@
     makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId);
 
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
             -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
             3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
-            bucketStartTimeNs, bucketStartTimeNs);
+            bucketStartTimeNs, bucketStartTimeNs, provider);
     durationProducer.mCondition = ConditionState::kFalse;
 
     assertConditionTimer(durationProducer.mConditionTimer, false, 0, 0);
@@ -200,12 +203,13 @@
     makeLogEvent(&event4, bucketStartTimeNs + bucketSizeNs + 3, tagId);
 
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
             -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
             3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
-            bucketStartTimeNs, bucketStartTimeNs);
+            bucketStartTimeNs, bucketStartTimeNs, provider);
 
     EXPECT_EQ(ConditionState::kUnknown, durationProducer.mCondition);
     EXPECT_FALSE(durationProducer.isConditionSliced());
@@ -248,11 +252,12 @@
     metric.set_split_bucket_for_app_upgrade(true);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -312,11 +317,12 @@
     metric.set_split_bucket_for_app_upgrade(true);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -377,11 +383,12 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     sp<AnomalyTracker> anomalyTracker =
             durationProducer.addAnomalyTracker(alert, alarmMonitor, UPDATE_NEW, bucketStartTimeNs);
@@ -425,11 +432,12 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     int64_t startTimeNs = bucketStartTimeNs + 1;
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -480,11 +488,12 @@
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     int64_t startTimeNs = bucketStartTimeNs + 1;
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -542,11 +551,12 @@
     metric.set_split_bucket_for_app_upgrade(false);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     DurationMetricProducer durationProducer(
             kConfigKey, metric, -1 /* no condition */, {}, -1 /*what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs);
+            wizard, protoHash, dimensions, bucketStartTimeNs, bucketStartTimeNs, provider);
 
     int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
     LogEvent event1(/*uid=*/0, /*pid=*/0);
@@ -585,6 +595,7 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     FieldMatcher dimensions;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeLogEvent(&event1, bucketStartTimeNs + 50, tagId);
@@ -599,7 +610,7 @@
             kConfigKey, metric, 0 /* condition index */, {ConditionState::kUnknown},
             -1 /*what index not needed*/, 1 /* start index */, 2 /* stop index */,
             3 /* stop_all index */, false /*nesting*/, wizard, protoHash, dimensions,
-            bucketStartTimeNs, bucketStartTimeNs);
+            bucketStartTimeNs, bucketStartTimeNs, provider);
 
     durationProducer.onConditionChanged(true /* condition */, bucketStartTimeNs + 5);
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
diff --git a/statsd/tests/metrics/EventMetricProducer_test.cpp b/statsd/tests/metrics/EventMetricProducer_test.cpp
index d5fe932..6e25f79 100644
--- a/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -80,9 +80,10 @@
     CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, provider);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
@@ -117,10 +118,11 @@
     CreateNoValuesLogEvent(&event2, 1 /*tagId*/, bucketStartTimeNs + 10);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/,
                                       {ConditionState::kUnknown}, wizard, protoHash,
-                                      bucketStartTimeNs);
+                                      bucketStartTimeNs, provider);
 
     eventProducer.onConditionChanged(true /*condition*/, bucketStartTimeNs);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
@@ -174,10 +176,11 @@
     EXPECT_CALL(*wizard, query(_, key1, _)).WillOnce(Return(ConditionState::kFalse));
     // Condition is true for second event.
     EXPECT_CALL(*wizard, query(_, key2, _)).WillOnce(Return(ConditionState::kTrue));
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     EventMetricProducer eventProducer(kConfigKey, metric, 0 /*condition index*/,
                                       {ConditionState::kUnknown}, wizard, protoHash,
-                                      bucketStartTimeNs);
+                                      bucketStartTimeNs, provider);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
@@ -213,8 +216,9 @@
     makeLogEvent(&event4, tagId, bucketStartTimeNs + 40, "222");
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, provider);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
@@ -265,8 +269,9 @@
     makeLogEvent(&event4, tagId, bucketStartTimeNs + 40, "111", &bytesField2);
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, provider);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
@@ -314,8 +319,9 @@
     makeLogEvent(&event3, tagId2, bucketStartTimeNs + 40, "222");
 
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EventMetricProducer eventProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
-                                      wizard, protoHash, bucketStartTimeNs);
+                                      wizard, protoHash, bucketStartTimeNs, provider);
 
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
     eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2);
@@ -343,6 +349,7 @@
         }
     }
 }
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 9f010a4..ff3d8d1 100644
--- a/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -98,13 +98,14 @@
             createEventMatcherWizard(tagId, logEventMatcherIndex);
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     // statsd started long ago.
     // The metric starts in the middle of the bucket
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       -1, -1, tagId, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     EXPECT_EQ(600500000000, gaugeProducer.mCurrentBucketStartTimeNs);
@@ -140,10 +141,12 @@
                 return true;
             }));
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
@@ -228,10 +231,12 @@
     sp<EventMatcherWizard> eventMatcherWizard =
             createEventMatcherWizard(tagId, logEventMatcherIndex);
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       -1 /* -1 means no pulling */, -1, tagId, bucketStartTimeNs,
-                                      bucketStartTimeNs, pullerManager);
+                                      bucketStartTimeNs, pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     sp<AnomalyTracker> anomalyTracker =
@@ -310,6 +315,8 @@
     sp<EventMatcherWizard> eventMatcherWizard =
             createEventMatcherWizard(tagId, logEventMatcherIndex);
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
     EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
@@ -326,7 +333,7 @@
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
@@ -392,10 +399,12 @@
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, bucketStartTimeNs, _))
             .WillOnce(Return(false));
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     vector<shared_ptr<LogEvent>> allData;
@@ -446,10 +455,12 @@
                 return true;
             }));
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/,
-                                      {ConditionState::kUnknown}, wizard, protoHash,
-                                      logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
-                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
+    GaugeMetricProducer gaugeProducer(
+            kConfigKey, metric, 0 /*condition index*/, {ConditionState::kUnknown}, wizard,
+            protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
+            bucketStartTimeNs, bucketStartTimeNs, pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     gaugeProducer.onConditionChanged(true, conditionChangeNs);
@@ -534,10 +545,12 @@
                 return true;
             }));
 
-    GaugeMetricProducer gaugeProducer(kConfigKey, metric, 0 /*condition index*/,
-                                      {ConditionState::kUnknown}, wizard, protoHash,
-                                      logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
-                                      bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
+    GaugeMetricProducer gaugeProducer(
+            kConfigKey, metric, 0 /*condition index*/, {ConditionState::kUnknown}, wizard,
+            protoHash, logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
+            bucketStartTimeNs, bucketStartTimeNs, pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     gaugeProducer.onSlicedConditionMayChange(true, sliceConditionChangeNs);
@@ -579,10 +592,12 @@
     sp<EventMatcherWizard> eventMatcherWizard =
             createEventMatcherWizard(tagId, logEventMatcherIndex);
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     Alert alert;
@@ -679,10 +694,12 @@
             .WillOnce(Return(true));
 
     int triggerId = 5;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     ASSERT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
@@ -737,10 +754,12 @@
                 return true;
             }));
 
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, /*triggerId=*/-1, tagId, bucketStartTimeNs,
-                                      bucketStartTimeNs, pullerManager);
+                                      bucketStartTimeNs, pullerManager, provider);
 
     EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size());
     gaugeProducer.prepareFirstBucket();
@@ -808,10 +827,12 @@
             .WillOnce(Return(true));
 
     int triggerId = 5;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     LogEvent triggerEvent(/*uid=*/0, /*pid=*/0);
@@ -878,10 +899,12 @@
             }));
 
     int triggerId = 5;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
     GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, {},
                                       wizard, protoHash, logEventMatcherIndex, eventMatcherWizard,
                                       tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
-                                      pullerManager);
+                                      pullerManager, provider);
     gaugeProducer.prepareFirstBucket();
 
     LogEvent triggerEvent(/*uid=*/0, /*pid=*/0);
@@ -914,7 +937,6 @@
     ShardOffsetProvider::getInstance().setShardOffset(5);
 
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
 
     int triggerId = 5;
     int shardCount = 2;
@@ -951,11 +973,11 @@
                 data->push_back(makeUidLogEvent(tagId, bucketStartTimeNs + 20, 1003, 18, 10));
                 return true;
             }));
-
-    GaugeMetricProducer gaugeProducer(kConfigKey, sampledGaugeMetric,
-                                      -1 /*-1 meaning no condition*/, {}, wizard, protoHash,
-                                      logEventMatcherIndex, eventMatcherWizard, tagId, triggerId,
-                                      tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    GaugeMetricProducer gaugeProducer(
+            kConfigKey, sampledGaugeMetric, -1 /*-1 meaning no condition*/, {}, wizard, protoHash,
+            logEventMatcherIndex, eventMatcherWizard, tagId, triggerId, tagId, bucketStartTimeNs,
+            bucketStartTimeNs, pullerManager, provider);
     SamplingInfo samplingInfo;
     samplingInfo.shardCount = shardCount;
     translateFieldMatcher(sampledGaugeMetric.dimensional_sampling_info().sampled_what_field(),
diff --git a/statsd/tests/metrics/KllMetricProducer_test.cpp b/statsd/tests/metrics/KllMetricProducer_test.cpp
index e84efb0..58d8a73 100644
--- a/statsd/tests/metrics/KllMetricProducer_test.cpp
+++ b/statsd/tests/metrics/KllMetricProducer_test.cpp
@@ -128,7 +128,8 @@
         translateFieldMatcher(metric.kll_field(), &fieldMatchers);
 
         const auto [dimensionSoftLimit, dimensionHardLimit] =
-                StatsdStats::getAtomDimensionKeySizeLimits(atomId);
+                StatsdStats::getAtomDimensionKeySizeLimits(
+                        atomId, StatsdStats::kDimensionKeySizeHardLimitMin);
 
         int conditionIndex = initialCondition ? 0 : -1;
         vector<ConditionState> initialConditionCache;
@@ -136,6 +137,7 @@
             initialConditionCache.push_back(initialCondition.value());
         }
 
+        sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
         return new KllMetricProducer(
                 kConfigKey, metric, protoHash, {/*pullAtomId=*/-1, /*pullerManager=*/nullptr},
                 {timeBaseNs, startTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
@@ -146,7 +148,7 @@
                 {conditionIndex, metric.links(), initialConditionCache, wizard},
                 {metric.state_link(), slicedStateAtoms, stateGroupMap},
                 {/*eventActivationMap=*/{}, /*eventDeactivationMap=*/{}},
-                {dimensionSoftLimit, dimensionHardLimit});
+                {dimensionSoftLimit, dimensionHardLimit}, provider);
     }
 
     static KllMetric createMetric() {
diff --git a/statsd/tests/metrics/MaxDurationTracker_test.cpp b/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 8367320..1df37e9 100644
--- a/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -67,14 +67,17 @@
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
                                bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
-    tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
+    tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     // Event starts again. This would not change anything as it already starts.
-    tracker.noteStart(key1, true, bucketStartTimeNs + 3, ConditionKey());
+    tracker.noteStart(key1, true, bucketStartTimeNs + 3, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     // Stopped.
     tracker.noteStop(key1, bucketStartTimeNs + 10, false);
 
     // Another event starts in this bucket.
-    tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey());
+    tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteStop(key2, bucketStartTimeNs + 40, false /*stop all*/);
 
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 1, emptyThreshold, &buckets);
@@ -101,10 +104,12 @@
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
                                bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
-    tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
+    tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
 
     // Another event starts in this bucket.
-    tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey());
+    tracker.noteStart(key2, true, bucketStartTimeNs + 20, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.flushIfNeeded(bucketStartTimeNs + bucketSizeNs + 40, emptyThreshold, &buckets);
     tracker.noteStopAll(bucketStartTimeNs + bucketSizeNs + 40);
     EXPECT_TRUE(tracker.mInfos.empty());
@@ -136,11 +141,12 @@
                                bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     // The event starts.
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
 
     // Starts again. Does not DEFAULT_DIMENSION_KEY anything.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + bucketSizeNs + 1,
-                      ConditionKey());
+                      ConditionKey(), StatsdStats::kDimensionKeySizeHardLimitMin);
 
     // The event stops at early 4th bucket.
     // Notestop is called from DurationMetricProducer's onMatchedLogEvent, which calls
@@ -175,8 +181,10 @@
                                bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     // 2 starts
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 10, ConditionKey());
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 10, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     // one stop
     tracker.noteStop(DEFAULT_DIMENSION_KEY, bucketStartTimeNs + 20, false /*stop all*/);
 
@@ -220,7 +228,8 @@
                                0, bucketStartTimeNs, bucketSizeNs, true, false, {});
     EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
-    tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
+    tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteConditionChanged(key1, true, conditionStarts1);
     tracker.noteConditionChanged(key1, false, conditionStops1);
     unordered_map<MetricDimensionKey, vector<DurationBucket>> buckets;
@@ -268,7 +277,8 @@
                                bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
                                {anomalyTracker});
 
-    tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
+    tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(53ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
 
@@ -327,10 +337,12 @@
                                bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
                                {anomalyTracker});
 
-    tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
+    tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteConditionChanged(key1, true, conditionStarts1);
     tracker.noteConditionChanged(key1, false, conditionStops1);
-    tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);  // Condition is on already.
+    tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);  // Condition is on already.
     tracker.noteConditionChanged(key1, true, conditionStarts2);
     ASSERT_EQ(1U, anomalyTracker->mAlarms.size());
     auto alarm = anomalyTracker->mAlarms.begin()->second;
@@ -352,7 +364,8 @@
     int64_t eventStopTimeNs = anomalyFireTimeSec * NS_PER_SEC + 10;
     tracker.noteStop(key1, eventStopTimeNs, false);
     tracker.noteStop(key2, eventStopTimeNs, false);
-    tracker.noteStart(key1, true, eventStopTimeNs + 1000000, conditionKey1);
+    tracker.noteStart(key1, true, eventStopTimeNs + 1000000, conditionKey1,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     // Anomaly is ongoing, but we're still in the refractory period.
     ASSERT_EQ(1U, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
@@ -361,7 +374,8 @@
     // Makes sure it is correct after the refractory period is over.
     tracker.noteStop(key1, eventStopTimeNs + 2000000, false);
     int64_t justBeforeRefPeriodNs = (refractoryPeriodEndsSec - 2) * NS_PER_SEC;
-    tracker.noteStart(key1, true, justBeforeRefPeriodNs, conditionKey1);
+    tracker.noteStart(key1, true, justBeforeRefPeriodNs, conditionKey1,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ(justBeforeRefPeriodNs + 40 * NS_PER_SEC,
                 (unsigned long long)(alarm->timestampSec * NS_PER_SEC));
@@ -409,8 +423,10 @@
                                bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
                                {anomalyTracker});
 
-    tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1);
-    tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);
+    tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
+    tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteStop(key1, eventStopTimeNs1, false);
     ASSERT_EQ(1U, anomalyTracker->mAlarms.size());
     auto alarm = anomalyTracker->mAlarms.begin()->second;
@@ -437,13 +453,15 @@
                                bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     // Duration below the gt_int threshold should not be added to past buckets.
-    tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(key1, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteStop(key1, eventStartTimeNs + thresholdDurationNs, false);
     tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, threshold, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
 
     // Duration above the gt_int threshold should be added to past buckets.
-    tracker.noteStart(key1, true, event2StartTimeNs, ConditionKey());
+    tracker.noteStart(key1, true, event2StartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteStop(key1, event2StartTimeNs + thresholdDurationNs + 1, false);
     tracker.flushIfNeeded(event2StartTimeNs + bucketSizeNs + 1, threshold, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -469,12 +487,14 @@
     MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
                                bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
-    tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
+    tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteStop(key1, bucketStartTimeNs + 50, false);
     EXPECT_TRUE(tracker.hasAccumulatedDuration());
     EXPECT_FALSE(tracker.hasStartedDuration());
 
-    tracker.noteStart(key1, true, bucketStartTimeNs + 100, ConditionKey());
+    tracker.noteStart(key1, true, bucketStartTimeNs + 100, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     EXPECT_TRUE(tracker.hasStartedDuration());
     tracker.noteConditionChanged(key1, false, bucketStartTimeNs + 150);
     EXPECT_TRUE(tracker.hasAccumulatedDuration());
@@ -485,6 +505,33 @@
     EXPECT_FALSE(tracker.hasAccumulatedDuration());
 }
 
+class MaxDurationTrackerTest_DimLimit : public Test {
+protected:
+    ~MaxDurationTrackerTest_DimLimit() {
+        StatsdStats::getInstance().reset();
+    }
+};
+
+TEST_F(MaxDurationTrackerTest_DimLimit, TestDimLimit) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1 /* conditionIndex */,
+                               false /* nesting */, 0 /* currentBucketStartNs */, 0 /* bucketNum */,
+                               0 /* startTimeNs */, bucketSizeNs, false /* conditionSliced */,
+                               false /* fullLink */, {} /* anomalyTrackers */);
+
+    const size_t dimensionHardLimit = 900;
+    for (int i = 1; i <= dimensionHardLimit; i++) {
+        const HashableDimensionKey key = getMockedDimensionKey(TagId, i, "maps");
+        tracker.noteStart(key, false /* condition */, i /* eventTime */, ConditionKey(),
+                          dimensionHardLimit);
+    }
+    ASSERT_FALSE(tracker.mHasHitGuardrail);
+    const HashableDimensionKey key = getMockedDimensionKey(TagId, dimensionHardLimit + 1, "maps");
+    tracker.noteStart(key, false /* condition */, dimensionHardLimit + 1 /* eventTime */,
+                      ConditionKey(), dimensionHardLimit);
+    EXPECT_TRUE(tracker.mHasHitGuardrail);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/metrics/NumericValueMetricProducer_test.cpp b/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
index 85c5dd1..972dc85 100644
--- a/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
+++ b/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
@@ -200,7 +200,8 @@
         translateFieldMatcher(metric.value_field(), &fieldMatchers);
 
         const auto [dimensionSoftLimit, dimensionHardLimit] =
-                StatsdStats::getAtomDimensionKeySizeLimits(tagId);
+                StatsdStats::getAtomDimensionKeySizeLimits(
+                        tagId, StatsdStats::kDimensionKeySizeHardLimitMin);
 
         int conditionIndex = conditionAfterFirstBucketPrepared ? 0 : -1;
         vector<ConditionState> initialConditionCache;
@@ -214,17 +215,27 @@
                         ? optional<int64_t>(metric.condition_correction_threshold_nanos())
                         : nullopt;
 
+        std::vector<ValueMetric::AggregationType> aggregationTypes;
+        if (metric.aggregation_types_size() != 0) {
+            for (int i = 0; i < metric.aggregation_types_size(); i++) {
+                aggregationTypes.push_back(metric.aggregation_types(i));
+            }
+        } else {  // aggregation_type() is set or default is used.
+            aggregationTypes.push_back(metric.aggregation_type());
+        }
+
+        sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
         sp<NumericValueMetricProducer> valueProducer = new NumericValueMetricProducer(
                 kConfigKey, metric, protoHash, {pullAtomId, pullerManager},
                 {timeBaseNs, startTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
                  conditionCorrectionThresholdNs, metric.split_bucket_for_app_upgrade()},
                 {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions,
                  logEventMatcherIndex, eventMatcherWizard, metric.dimensions_in_what(),
-                 fieldMatchers},
+                 fieldMatchers, aggregationTypes},
                 {conditionIndex, metric.links(), initialConditionCache, wizard},
                 {metric.state_link(), slicedStateAtoms, stateGroupMap},
                 {/*eventActivationMap=*/{}, /*eventDeactivationMap=*/{}},
-                {dimensionSoftLimit, dimensionHardLimit});
+                {dimensionSoftLimit, dimensionHardLimit}, provider);
 
         valueProducer->prepareFirstBucket();
         if (conditionAfterFirstBucketPrepared) {
@@ -7733,6 +7744,266 @@
                         0);  // Diff of 15 and 18
 }
 
+TEST(NumericValueMetricProducerTest, TestMultipleAggTypesPulled) {
+    ValueMetric metric = NumericValueMetricProducerTestHelper::createMetricWithCondition();
+    // createMetricWithCondition() adds field 2 as first value field.
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(1);
+    metric.add_aggregation_types(ValueMetric::MIN);
+    metric.add_aggregation_types(ValueMetric::MAX);
+    metric.add_aggregation_types(ValueMetric::SUM);
+    metric.add_aggregation_types(ValueMetric::AVG);
+    metric.add_aggregation_types(ValueMetric::SUM);
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
+            // Screen On Pull 1.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 1, 2));
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 2, 4));
+                return true;
+            }))
+            // Screen Off Pull 2.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, 3, 5));
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, 4, 9));
+                return true;
+            }))
+            // Screen On Pull 3.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 5, 10));
+                data->push_back(
+                        CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 6, 20));
+                return true;
+            }))
+            // Dump report pull.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 55 * NS_PER_SEC,
+                                                       25, 60));
+                data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 55 * NS_PER_SEC,
+                                                       35, 80));
+
+                return true;
+            }));
+
+    sp<NumericValueMetricProducer> valueProducer =
+            NumericValueMetricProducerTestHelper::createValueProducerWithCondition(
+                    pullerManager, metric, ConditionState::kFalse);
+
+    EXPECT_EQ(5, valueProducer->mFieldMatchers.size());
+    ASSERT_EQ(5, valueProducer->mAggregationTypes.size());
+    EXPECT_EQ(ValueMetric::MIN, valueProducer->mAggregationTypes[0]);
+    EXPECT_EQ(ValueMetric::MAX, valueProducer->mAggregationTypes[1]);
+    EXPECT_EQ(ValueMetric::SUM, valueProducer->mAggregationTypes[2]);
+    EXPECT_EQ(ValueMetric::AVG, valueProducer->mAggregationTypes[3]);
+    EXPECT_EQ(ValueMetric::SUM, valueProducer->mAggregationTypes[4]);
+
+    // Screen On. Pull 1.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 30 * NS_PER_SEC);
+
+    // Screen Off.
+    valueProducer->onConditionChanged(false, bucketStartTimeNs + 40 * NS_PER_SEC);
+
+    // Screen On. Pull 2.
+    valueProducer->onConditionChanged(true, bucketStartTimeNs + 50 * NS_PER_SEC);
+
+    // Bucket 2 start. Pull 4.
+    vector<shared_ptr<LogEvent>> allData;
+    allData.clear();
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 15, 30));
+    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs, 20, 40));
+    valueProducer->onDataPulled(allData, PullResult::PULL_RESULT_SUCCESS, bucket2StartTimeNs);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucket2StartTimeNs + 55 * NS_PER_SEC;
+    valueProducer->onDumpReport(dumpReportTimeNs, true /* include current buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    backfillDimensionPath(&report);
+    backfillStartEndTimestamp(&report);
+    EXPECT_TRUE(report.has_value_metrics());
+    StatsLogReport::ValueMetricDataWrapper valueMetrics;
+    sortMetricDataByDimensionsValue(report.value_metrics(), &valueMetrics);
+    ASSERT_EQ(1, valueMetrics.data_size());
+    EXPECT_EQ(0, report.value_metrics().skipped_size());
+
+    // Bucket 1.
+    // Value field 1
+    // Diff from pulls 1 and 2: (3+4)-(1+2) = 4
+    // Diff from pulls 3 and 4: (15+20)-(5+6) = 24
+
+    // Value field 2
+    // Diff from pulls 1 and 2: (5+9)-(2+4) = 8
+    // Diff from pulls 3 and 4: (30+40)-(10+20) = 40
+
+    // Bucket 2
+    // Value field 1
+    // Diff from pulls 4 and 5: (25+35)-(15+20) = 25
+
+    // Value field 2
+    // Diff from pulls 4 and 5: (60+80)-(30+40) = 70
+
+    // Output values are calculated for these agg type - value field combinations
+    // MIN-2, MAX-2, SUM-2, AVG-2, SUM-1
+    ValueMetricData data = valueMetrics.data(0);
+    ASSERT_EQ(2, data.bucket_info_size());
+    ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucket2StartTimeNs,
+                        {8, 40, 48, 24, 28}, 20 * NS_PER_SEC, 0);
+    ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, dumpReportTimeNs,
+                        {70, 70, 70, 70, 25}, 55 * NS_PER_SEC, 0);
+}
+
+TEST(NumericValueMetricProducerTest, TestMultipleAggTypesPushed) {
+    ValueMetric metric = NumericValueMetricProducerTestHelper::createMetric();
+    metric.mutable_dimensions_in_what()->set_field(tagId);
+    metric.mutable_dimensions_in_what()->add_child()->set_field(1);
+    // createMetric() adds field 2 as first value field.
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(3);
+    metric.add_aggregation_types(ValueMetric::MIN);
+    metric.add_aggregation_types(ValueMetric::MAX);
+    metric.add_aggregation_types(ValueMetric::SUM);
+    metric.add_aggregation_types(ValueMetric::AVG);
+    metric.add_aggregation_types(ValueMetric::SUM);
+
+    sp<EventMatcherWizard> eventMatcherWizard =
+            createEventMatcherWizard(tagId, logEventMatcherIndex);
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    sp<NumericValueMetricProducer> valueProducer =
+            NumericValueMetricProducerTestHelper::createValueProducerNoConditions(
+                    pullerManager, metric, /*pullAtomId=*/-1);
+
+    EXPECT_EQ(5, valueProducer->mFieldMatchers.size());
+    ASSERT_EQ(5, valueProducer->mAggregationTypes.size());
+    EXPECT_EQ(ValueMetric::MIN, valueProducer->mAggregationTypes[0]);
+    EXPECT_EQ(ValueMetric::MAX, valueProducer->mAggregationTypes[1]);
+    EXPECT_EQ(ValueMetric::SUM, valueProducer->mAggregationTypes[2]);
+    EXPECT_EQ(ValueMetric::AVG, valueProducer->mAggregationTypes[3]);
+    EXPECT_EQ(ValueMetric::SUM, valueProducer->mAggregationTypes[4]);
+
+    // Bucket 1 events.
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 5, 10);
+
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 6, 8);
+
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event3, tagId, bucketStartTimeNs + 40, 2, 3, 10);
+
+    LogEvent event4(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event4, tagId, bucketStartTimeNs + 50, 2, 4, 6);
+
+    LogEvent event5(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event5, tagId, bucketStartTimeNs + 30, 1, 19, 9);
+
+    LogEvent event6(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event6, tagId, bucketStartTimeNs + 60, 2, 20, 8);
+
+    // Bucket 2 events.
+    LogEvent event7(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event7, tagId, bucket2StartTimeNs + 10, 2, 7, 41);
+
+    LogEvent event8(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event8, tagId, bucket2StartTimeNs + 20, 1, 21, 40);
+
+    LogEvent event9(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event9, tagId, bucket2StartTimeNs + 30, 1, 10, 4);
+
+    LogEvent event10(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event10, tagId, bucket2StartTimeNs + 40, 2, 3, 50);
+
+    LogEvent event11(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event11, tagId, bucket2StartTimeNs + 50, 1, 20, 7);
+
+    LogEvent event12(/*uid=*/0, /*pid=*/0);
+    CreateThreeValueLogEvent(&event12, tagId, bucket2StartTimeNs + 60, 2, 20, 2);
+
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event1);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event2);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event3);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event4);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event5);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event6);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event7);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event8);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event9);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event10);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event11);
+    valueProducer->onMatchedLogEvent(1 /*log matcher index*/, event12);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    valueProducer->onDumpReport(bucket3StartTimeNs + 10000, false /* include recent buckets */,
+                                true, FAST /* dumpLatency */, nullptr, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    backfillDimensionPath(&report);
+    backfillStartEndTimestamp(&report);
+    EXPECT_TRUE(report.has_value_metrics());
+    StatsLogReport::ValueMetricDataWrapper valueMetrics;
+    sortMetricDataByDimensionsValue(report.value_metrics(), &valueMetrics);
+    ASSERT_EQ(2, valueMetrics.data_size());
+    EXPECT_EQ(0, report.value_metrics().skipped_size());
+
+    // Bucket 1.
+    // Value field 2
+    // dim 1 pushed values: 5, 6, 19
+    // dim 2 pushed values: 3, 4, 20
+
+    // Value field 3
+    // dim 1 pushed values: 10, 8, 9
+    // dim 2 pushed values: 10, 6, 8
+
+    // Bucket 2
+    // Value field 2
+    // dim 1 pushed values: 21, 10, 20
+    // dim 2 pushed values: 7, 3, 20
+
+    // Value field 3
+    // dim 1 pushed values: 40, 4, 7
+    // dim 2 pushed values: 41, 50, 2
+
+    // Output values are calculated for these agg type - value field combinations
+    // MIN-2, MAX-2, SUM-2, AVG-2, SUM-1
+    ValueMetricData data = valueMetrics.data(0);
+    ASSERT_EQ(2, data.bucket_info_size());
+    ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucket2StartTimeNs,
+                        {5, 19, 30, 10, 27}, 0, 0);
+    ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, bucket3StartTimeNs,
+                        {10, 21, 51, 17, 51}, 0, 0);
+
+    data = valueMetrics.data(1);
+    ASSERT_EQ(2, data.bucket_info_size());
+    ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucket2StartTimeNs,
+                        {3, 20, 27, 9, 24}, 0, 0);
+    ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, bucket3StartTimeNs,
+                        {3, 20, 30, 10, 93}, 0, 0);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/metrics/OringDurationTracker_test.cpp b/statsd/tests/metrics/OringDurationTracker_test.cpp
index 6d4f0a2..7fce270 100644
--- a/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -66,9 +66,11 @@
                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
                                  false, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);  // overlapping wl
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
 
     tracker.noteStop(kEventKey1, eventStartTimeNs + durationTimeNs, false);
@@ -96,8 +98,10 @@
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
                                  bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);  // overlapping wl
 
     tracker.noteStop(kEventKey1, eventStartTimeNs + 2000, false);
     tracker.noteStop(kEventKey1, eventStartTimeNs + 2003, false);
@@ -127,8 +131,10 @@
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
                                  bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
-    tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
+    tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);  // overlapping wl
 
     tracker.noteStopAll(eventStartTimeNs + 2003);
 
@@ -156,10 +162,12 @@
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
                                  bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
     tracker.flushIfNeeded(eventStartTimeNs + 2 * bucketSizeNs, emptyThreshold, &buckets);
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2 * bucketSizeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     EXPECT_EQ((long long)(bucketStartTimeNs + 2 * bucketSizeNs), tracker.mLastStartTime);
 
     ASSERT_EQ(2u, buckets[eventKey].size());
@@ -200,7 +208,8 @@
                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
                                  true, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
 
     tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
 
@@ -239,7 +248,8 @@
                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
                                  true, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     // condition to false; record duration 5n
     tracker.onSlicedConditionMayChange(eventStartTimeNs + 5);
     // condition to true.
@@ -276,8 +286,10 @@
     OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
                                  bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
 
     tracker.noteStop(kEventKey1, eventStartTimeNs + 3, false);
 
@@ -318,7 +330,8 @@
                                  {anomalyTracker});
 
     // Nothing in the past bucket.
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     EXPECT_EQ((long long)(alert.trigger_if_sum_gt() + eventStartTimeNs),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
 
@@ -326,7 +339,8 @@
     ASSERT_EQ(0u, buckets[eventKey].size());
 
     int64_t event1StartTimeNs = eventStartTimeNs + 10;
-    tracker.noteStart(kEventKey1, true, event1StartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, event1StartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     // No past buckets. The anomaly will happen in bucket #0.
     EXPECT_EQ((long long)(event1StartTimeNs + alert.trigger_if_sum_gt() - 3),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, event1StartTimeNs));
@@ -345,7 +359,8 @@
 
     // One past buckets. The anomaly will happen in bucket #1.
     int64_t event2StartTimeNs = eventStartTimeNs + bucketSizeNs + 15;
-    tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     EXPECT_EQ((long long)(event2StartTimeNs + alert.trigger_if_sum_gt() - bucket0Duration -
                           bucket1Duration),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, event2StartTimeNs));
@@ -354,7 +369,8 @@
     // Only one past buckets is applicable. Bucket +0 should be trashed. The anomaly will happen in
     // bucket #2.
     int64_t event3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs - 9 * NS_PER_SEC;
-    tracker.noteStart(kEventKey1, true, event3StartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, event3StartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     EXPECT_EQ((long long)(event3StartTimeNs + alert.trigger_if_sum_gt() - bucket1Duration - 1LL),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, event3StartTimeNs));
 }
@@ -380,7 +396,8 @@
                                  bucketSizeNs, true, false, {anomalyTracker});
 
     int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
-    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     // Anomaly happens in the bucket #1.
     EXPECT_EQ((long long)(bucketStartTimeNs + 14 * NS_PER_SEC),
               tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
@@ -424,7 +441,8 @@
                                          bucketSizeNs, true, false, {anomalyTracker});
 
             int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
-            tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
+            tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey(),
+                              StatsdStats::kDimensionKeySizeHardLimitMin);
             EXPECT_EQ((long long)(eventStartTimeNs + thresholdNs),
                       tracker.predictAnomalyTimestampNs(*anomalyTracker, eventStartTimeNs));
             int64_t eventStopTimeNs = eventStartTimeNs + thresholdNs + NS_PER_SEC;
@@ -437,7 +455,8 @@
 
             // Acquire and release a wakelock in the next bucket.
             int64_t event2StartTimeNs = eventStopTimeNs + bucketSizeNs;
-            tracker.noteStart(DEFAULT_DIMENSION_KEY, true, event2StartTimeNs, ConditionKey());
+            tracker.noteStart(DEFAULT_DIMENSION_KEY, true, event2StartTimeNs, ConditionKey(),
+                              StatsdStats::kDimensionKeySizeHardLimitMin);
             int64_t event2StopTimeNs = event2StartTimeNs + 4 * NS_PER_SEC;
             tracker.noteStop(DEFAULT_DIMENSION_KEY, event2StopTimeNs, false);
 
@@ -481,7 +500,8 @@
                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
                                  false, false, {anomalyTracker});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
     EXPECT_TRUE(tracker.mStarted.empty());
@@ -489,7 +509,8 @@
 
     ASSERT_EQ(0u, tracker.mStarted.size());
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 20, ConditionKey());
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs + 20, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
     EXPECT_EQ((long long)(52ULL * NS_PER_SEC),  // (10s + 1s + 1ns + 20ns) - 10ns + 40s, rounded up
               (long long)(anomalyTracker->mAlarms.begin()->second->timestampSec * NS_PER_SEC));
@@ -530,7 +551,8 @@
                                  bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false,
                                  false, {anomalyTracker});
 
-    tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey);  // start key1
+    tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);  // start key1
     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
@@ -540,13 +562,15 @@
     ASSERT_EQ(0u, anomalyTracker->mAlarms.size());
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
 
-    tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey);  // start key1 again
+    tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);  // start key1 again
     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
 
-    tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey);  // start key2
+    tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey,
+                      StatsdStats::kDimensionKeySizeHardLimitMin);  // start key2
     ASSERT_EQ(1u, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
@@ -589,13 +613,15 @@
                                  false, false, {});
 
     // Duration below the gt_int threshold should not be added to past buckets.
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteStop(kEventKey1, eventStartTimeNs + thresholdDurationNs, false);
     tracker.flushIfNeeded(eventStartTimeNs + bucketSizeNs + 1, threshold, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) == buckets.end());
 
     // Duration above the gt_int threshold should be added to past buckets.
-    tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, event2StartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteStop(kEventKey1, event2StartTimeNs + thresholdDurationNs + 1, false);
     tracker.flushIfNeeded(event2StartTimeNs + bucketSizeNs + 1, threshold, &buckets);
     EXPECT_TRUE(buckets.find(eventKey) != buckets.end());
@@ -627,11 +653,13 @@
                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
                                  false, false, {anomalyTracker});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteStop(kEventKey1, bucketStartTimeNs + 50, false);
     EXPECT_TRUE(tracker.hasAccumulatedDuration());
 
-    tracker.noteStart(kEventKey1, true, bucketStartTimeNs + 100, ConditionKey());
+    tracker.noteStart(kEventKey1, true, bucketStartTimeNs + 100, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     EXPECT_FALSE(tracker.mStarted.empty());
     tracker.onConditionChanged(false, bucketStartTimeNs + 150);
     EXPECT_TRUE(tracker.mStarted.empty());
@@ -669,7 +697,8 @@
                                  bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
                                  false, false, {});
 
-    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
+    tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey(),
+                      StatsdStats::kDimensionKeySizeHardLimitMin);
     tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
     tracker.flushCurrentBucket(eventStartTimeNs + 20, emptyThreshold, 0, &buckets);
 
@@ -679,6 +708,34 @@
     EXPECT_FALSE(tracker.hasAccumulatedDuration());
 }
 
+class OringDurationTrackerTest_DimLimit : public Test {
+protected:
+    ~OringDurationTrackerTest_DimLimit() {
+        StatsdStats::getInstance().reset();
+    }
+};
+
+TEST_F(OringDurationTrackerTest_DimLimit, TestDimLimit) {
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1 /* conditionIndex */,
+                                 false /* nesting */, 0 /* currentBucketStartNs */,
+                                 0 /* currentBucketNum */, 0 /* startTimeNs */, bucketSizeNs,
+                                 true /* conditionSliced */, false /* fullLink */,
+                                 {} /* anomalyTrackers */);
+
+    const size_t dimensionHardLimit = 900;
+    for (int i = 1; i <= dimensionHardLimit; i++) {
+        const HashableDimensionKey key = getMockedDimensionKey(TagId, i, "maps");
+        tracker.noteStart(key, false /* condition */, i /* eventTime */, ConditionKey(),
+                          dimensionHardLimit);
+    }
+    ASSERT_FALSE(tracker.mHasHitGuardrail);
+    const HashableDimensionKey key = getMockedDimensionKey(TagId, dimensionHardLimit + 1, "maps");
+    tracker.noteStart(key, false /* condition */, dimensionHardLimit + 1 /* eventTime */,
+                      ConditionKey(), dimensionHardLimit);
+    EXPECT_TRUE(tracker.mHasHitGuardrail);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp b/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp
new file mode 100644
index 0000000..2085e11
--- /dev/null
+++ b/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp
@@ -0,0 +1,270 @@
+#include "src/metrics/RestrictedEventMetricProducer.h"
+
+#include <gtest/gtest.h>
+
+#include "flags/FlagProvider.h"
+#include "metrics_test_helper.h"
+#include "stats_annotations.h"
+#include "tests/statsd_test_util.h"
+#include "utils/DbUtils.h"
+
+using namespace testing;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+const ConfigKey configKey(/*uid=*/0, /*id=*/12345);
+const int64_t metricId1 = 123;
+const int64_t metricId2 = 456;
+
+bool metricTableExist(int64_t metricId) {
+    stringstream query;
+    query << "SELECT * FROM metric_" << metricId;
+    vector<int32_t> columnTypes;
+    vector<vector<string>> rows;
+    vector<string> columnNames;
+    string err;
+    return dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err);
+}
+}  // anonymous namespace
+
+class RestrictedEventMetricProducerTest : public Test {
+protected:
+    void SetUp() override {
+        if (!isAtLeastU()) {
+            GTEST_SKIP();
+        }
+    }
+    void TearDown() override {
+        if (!isAtLeastU()) {
+            GTEST_SKIP();
+        }
+        dbutils::deleteDb(configKey);
+        FlagProvider::getInstance().resetOverrides();
+    }
+};
+
+TEST_F(RestrictedEventMetricProducerTest, TestOnMatchedLogEventMultipleEvents) {
+    EventMetric metric;
+    metric.set_id(metricId1);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0, provider);
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/1);
+    std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/3);
+
+    producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
+    producer.onMatchedLogEvent(/*matcherIndex=*/1, *event2);
+    producer.flushRestrictedData();
+
+    stringstream query;
+    query << "SELECT * FROM metric_" << metricId1;
+    string err;
+    vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    vector<vector<string>> rows;
+    dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err);
+    ASSERT_EQ(rows.size(), 2);
+    EXPECT_EQ(columnTypes.size(),
+              3 + event1->getValues().size());  // col 0:2 are reserved for metadata.
+    EXPECT_EQ(/*tagId=*/rows[0][0], to_string(event1->GetTagId()));
+    EXPECT_EQ(/*elapsedTimestampNs=*/rows[0][1], to_string(event1->GetElapsedTimestampNs()));
+    EXPECT_EQ(/*elapsedTimestampNs=*/rows[1][1], to_string(event2->GetElapsedTimestampNs()));
+
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestOnMatchedLogEventMultipleFields) {
+    EventMetric metric;
+    metric.set_id(metricId2);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0, provider);
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, 1);
+    AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+                                   ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+    AStatsEvent_overwriteTimestamp(statsEvent, 1);
+
+    AStatsEvent_writeString(statsEvent, "111");
+    AStatsEvent_writeInt32(statsEvent, 11);
+    AStatsEvent_writeFloat(statsEvent, 11.0);
+    LogEvent logEvent(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, &logEvent);
+
+    producer.onMatchedLogEvent(/*matcherIndex=1*/ 1, logEvent);
+    producer.flushRestrictedData();
+
+    stringstream query;
+    query << "SELECT * FROM metric_" << metricId2;
+    string err;
+    vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    vector<vector<string>> rows;
+    EXPECT_TRUE(dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err));
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_EQ(columnTypes.size(),
+              3 + logEvent.getValues().size());  // col 0:2 are reserved for metadata.
+    EXPECT_EQ(/*field1=*/rows[0][3], "111");
+    EXPECT_EQ(/*field2=*/rows[0][4], "11");
+    EXPECT_FLOAT_EQ(/*field3=*/std::stof(rows[0][5]), 11.0);
+
+    EXPECT_THAT(columnNames, ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs",
+                                         "field_1", "field_2", "field_3"));
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestOnMatchedLogEventWithCondition) {
+    EventMetric metric;
+    metric.set_id(metricId1);
+    metric.set_condition(StringToId("SCREEN_ON"));
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/0,
+                                           /*initialConditionCache=*/{ConditionState::kUnknown},
+                                           new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0, provider);
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/1);
+    std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/3);
+
+    producer.onConditionChanged(true, 0);
+    producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
+    producer.onConditionChanged(false, 1);
+    producer.onMatchedLogEvent(/*matcherIndex=*/1, *event2);
+    producer.flushRestrictedData();
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << metricId1;
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err);
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_EQ(columnTypes.size(), 3 + event1->getValues().size());
+    EXPECT_EQ(/*elapsedTimestampNs=*/rows[0][1], to_string(event1->GetElapsedTimestampNs()));
+
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestOnDumpReportNoOp) {
+    EventMetric metric;
+    metric.set_id(metricId1);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0, provider);
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*timestampNs=*/1);
+    producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    producer.onDumpReport(/*dumpTimeNs=*/10,
+                          /*include_current_partial_bucket=*/true,
+                          /*erase_data=*/true, FAST, &strSet, &output);
+
+    ASSERT_EQ(output.size(), 0);
+    ASSERT_EQ(strSet.size(), 0);
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestOnMetricRemove) {
+    EventMetric metric;
+    metric.set_id(metricId1);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0, provider);
+    EXPECT_FALSE(metricTableExist(metricId1));
+
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*timestampNs=*/1);
+    producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
+    producer.flushRestrictedData();
+    EXPECT_TRUE(metricTableExist(metricId1));
+
+    producer.onMetricRemove();
+    EXPECT_FALSE(metricTableExist(metricId1));
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestRestrictedEventMetricTtlDeletesFirstEvent) {
+    EventMetric metric;
+    metric.set_id(metricId1);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0, provider);
+
+    int64_t currentTimeNs = getWallClockNs();
+    int64_t eightDaysAgo = currentTimeNs - 8 * 24 * 3600 * NS_PER_SEC;
+    std::unique_ptr<LogEvent> event1 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/1);
+    event1->setLogdWallClockTimestampNs(eightDaysAgo);
+    std::unique_ptr<LogEvent> event2 = CreateRestrictedLogEvent(/*atomTag=*/123, /*timestampNs=*/3);
+    event2->setLogdWallClockTimestampNs(currentTimeNs);
+
+    producer.onMatchedLogEvent(/*matcherIndex=*/1, *event1);
+    producer.onMatchedLogEvent(/*matcherIndex=*/1, *event2);
+    producer.flushRestrictedData();
+    sqlite3* dbHandle = dbutils::getDb(configKey);
+    producer.enforceRestrictedDataTtl(dbHandle, currentTimeNs + 100);
+    dbutils::closeDb(dbHandle);
+
+    std::stringstream query;
+    query << "SELECT * FROM metric_" << metricId1;
+    string err;
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    dbutils::query(configKey, query.str(), rows, columnTypes, columnNames, err);
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_EQ(columnTypes.size(), 3 + event1->getValues().size());
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+    EXPECT_THAT(rows[0], ElementsAre(to_string(event2->GetTagId()),
+                                     to_string(event2->GetElapsedTimestampNs()),
+                                     to_string(currentTimeNs), _));
+}
+
+TEST_F(RestrictedEventMetricProducerTest, TestLoadMetricMetadataSetsCategory) {
+    metadata::MetricMetadata metricMetadata;
+    metricMetadata.set_metric_id(metricId1);
+    metricMetadata.set_restricted_category(1);  // CATEGORY_DIAGNOSTIC
+    EventMetric metric;
+    metric.set_id(metricId1);
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0, provider);
+
+    producer.loadMetricMetadataFromProto(metricMetadata);
+
+    EXPECT_EQ(producer.getRestrictionCategory(), CATEGORY_DIAGNOSTIC);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/statsd/tests/metrics/metrics_test_helper.h b/statsd/tests/metrics/metrics_test_helper.h
index 39232c1..d750149 100644
--- a/statsd/tests/metrics/metrics_test_helper.h
+++ b/statsd/tests/metrics/metrics_test_helper.h
@@ -34,18 +34,18 @@
 class MockStatsPullerManager : public StatsPullerManager {
 public:
     MOCK_METHOD5(RegisterReceiver,
-                 void(int tagId, const ConfigKey& key, wp<PullDataReceiver> receiver,
+                 void(int tagId, const ConfigKey& key, const wp<PullDataReceiver>& receiver,
                       int64_t nextPulltimeNs, int64_t intervalNs));
     MOCK_METHOD3(UnRegisterReceiver,
-                 void(int tagId, const ConfigKey& key, wp<PullDataReceiver> receiver));
-    MOCK_METHOD4(Pull, bool(const int pullCode, const ConfigKey& key, const int64_t eventTimeNs,
+                 void(int tagId, const ConfigKey& key, const wp<PullDataReceiver>& receiver));
+    MOCK_METHOD4(Pull, bool(const int pullCode, const ConfigKey& key, int64_t eventTimeNs,
                             vector<std::shared_ptr<LogEvent>>* data));
     MOCK_METHOD4(Pull, bool(const int pullCode, const vector<int32_t>& uids,
                             const int64_t eventTimeNs, vector<std::shared_ptr<LogEvent>>* data));
     MOCK_METHOD2(RegisterPullUidProvider,
-                 void(const ConfigKey& configKey, wp<PullUidProvider> provider));
+                 void(const ConfigKey& configKey, const wp<PullUidProvider>& provider));
     MOCK_METHOD2(UnregisterPullUidProvider,
-                 void(const ConfigKey& configKey, wp<PullUidProvider> provider));
+                 void(const ConfigKey& configKey, const wp<PullUidProvider>& provider));
 };
 
 HashableDimensionKey getMockedDimensionKey(int tagId, int key, std::string value);
@@ -55,7 +55,7 @@
 MetricDimensionKey getMockedStateDimensionKey(int tagId, int key, int64_t value);
 
 // Utils to build FieldMatcher proto for simple one-depth atoms.
-void buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum, FieldMatcher* matcher);
+void buildSimpleAtomFieldMatcher(const int tagId, int atomFieldNum, FieldMatcher* matcher);
 void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher);
 
 }  // namespace statsd
diff --git a/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index e083cab..0861812 100644
--- a/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -26,6 +26,7 @@
 #include "src/condition/CombinationConditionTracker.h"
 #include "src/condition/SimpleConditionTracker.h"
 #include "src/matchers/CombinationAtomMatchingTracker.h"
+#include "src/metrics/CountMetricProducer.h"
 #include "src/metrics/DurationMetricProducer.h"
 #include "src/metrics/GaugeMetricProducer.h"
 #include "src/metrics/KllMetricProducer.h"
@@ -52,7 +53,8 @@
 
 namespace {
 
-ConfigKey key(123, 456);
+const int configId = 456;
+const ConfigKey key(123, configId);
 const int64_t timeBaseNs = 1000 * NS_PER_SEC;
 
 sp<UidMap> uidMap = new UidMap();
@@ -62,6 +64,7 @@
         /*minDiffToUpdateRegisteredAlarmTimeSec=*/0,
         [](const shared_ptr<IStatsCompanionService>&, int64_t) {},
         [](const shared_ptr<IStatsCompanionService>&) {});
+sp<ConfigMetadataProvider> configMetadataProvider;
 unordered_map<int, vector<int>> allTagIdsToMatchersMap;
 vector<sp<AtomMatchingTracker>> oldAtomMatchingTrackers;
 unordered_map<int64_t, int> oldAtomMatchingTrackerMap;
@@ -85,10 +88,11 @@
     // initStatsdConfig returns nullopt if config is valid
     return !initStatsdConfig(
                     key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-                    timeBaseNs, timeBaseNs, allTagIdsToMatchersMap, oldAtomMatchingTrackers,
-                    oldAtomMatchingTrackerMap, oldConditionTrackers, oldConditionTrackerMap,
-                    oldMetricProducers, oldMetricProducerMap, oldAnomalyTrackers, oldAlarmTrackers,
-                    tmpConditionToMetricMap, tmpTrackerToMetricMap, tmpTrackerToConditionMap,
+                    timeBaseNs, timeBaseNs, configMetadataProvider, allTagIdsToMatchersMap,
+                    oldAtomMatchingTrackers, oldAtomMatchingTrackerMap, oldConditionTrackers,
+                    oldConditionTrackerMap, oldMetricProducers, oldMetricProducerMap,
+                    oldAnomalyTrackers, oldAlarmTrackers, tmpConditionToMetricMap,
+                    tmpTrackerToMetricMap, tmpTrackerToConditionMap,
                     tmpActivationAtomTrackerToMetricMap, tmpDeactivationAtomTrackerToMetricMap,
                     oldAlertTrackerMap, metricsWithActivation, oldStateHashes, noReportMetricIds)
                     .has_value();
@@ -109,8 +113,6 @@
     return result;
 }
 
-}  // anonymous namespace
-
 class ConfigUpdateTest : public ::testing::Test {
 public:
     void SetUp() override {
@@ -136,6 +138,29 @@
     }
 };
 
+struct DimLimitTestCase {
+    int oldLimit;
+    int newLimit;
+    int actualLimit;
+
+    friend void PrintTo(const DimLimitTestCase& testCase, ostream* os) {
+        *os << testCase.oldLimit << "To" << testCase.newLimit;
+    }
+};
+
+class ConfigUpdateDimLimitTest : public ConfigUpdateTest,
+                                 public WithParamInterface<DimLimitTestCase> {};
+
+const vector<DimLimitTestCase> dimLimitTestCases = {
+        {900, 900, 900}, {1000, 850, 850},   {1100, 1500, 1500},
+        {800, 799, 800}, {3000, 3001, 3000}, {800, 0, 800},
+};
+
+INSTANTIATE_TEST_SUITE_P(DimLimit, ConfigUpdateDimLimitTest, ValuesIn(dimLimitTestCases),
+                         PrintToStringParamName());
+
+}  // anonymous namespace
+
 TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) {
     StatsdConfig config;
     AtomMatcher matcher = CreateSimpleAtomMatcher("TEST", /*atom=*/10);
@@ -146,7 +171,7 @@
     EXPECT_TRUE(initConfig(config));
 
     vector<UpdateStatus> matchersToUpdate(1, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(1, false);
+    vector<uint8_t> cycleTracker(1, false);
     unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     newAtomMatchingTrackerMap[matcherId] = 0;
     EXPECT_EQ(determineMatcherUpdateStatus(config, 0, oldAtomMatchingTrackerMap,
@@ -171,7 +196,7 @@
     *newConfig.add_atom_matcher() = newMatcher;
 
     vector<UpdateStatus> matchersToUpdate(1, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(1, false);
+    vector<uint8_t> cycleTracker(1, false);
     unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     newAtomMatchingTrackerMap[matcherId] = 0;
     EXPECT_EQ(determineMatcherUpdateStatus(newConfig, 0, oldAtomMatchingTrackerMap,
@@ -196,7 +221,7 @@
     *newConfig.add_atom_matcher() = newMatcher;
 
     vector<UpdateStatus> matchersToUpdate(1, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(1, false);
+    vector<uint8_t> cycleTracker(1, false);
     unordered_map<int64_t, int> newAtomMatchingTrackerMap;
     newAtomMatchingTrackerMap[matcherId] = 0;
     EXPECT_EQ(determineMatcherUpdateStatus(newConfig, 0, oldAtomMatchingTrackerMap,
@@ -238,7 +263,7 @@
     newAtomMatchingTrackerMap[matcher1Id] = 2;
 
     vector<UpdateStatus> matchersToUpdate(3, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(3, false);
+    vector<uint8_t> cycleTracker(3, false);
     // Only update the combination. It should recurse the two child matchers and preserve all 3.
     EXPECT_EQ(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap,
                                            oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
@@ -283,7 +308,7 @@
     newAtomMatchingTrackerMap[matcher1Id] = 2;
 
     vector<UpdateStatus> matchersToUpdate(3, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(3, false);
+    vector<uint8_t> cycleTracker(3, false);
     // Only update the combination. The simple matchers should not be evaluated.
     EXPECT_EQ(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap,
                                            oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
@@ -328,7 +353,7 @@
     newAtomMatchingTrackerMap[matcher1Id] = 2;
 
     vector<UpdateStatus> matchersToUpdate(3, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(3, false);
+    vector<uint8_t> cycleTracker(3, false);
     // Only update the combination.
     EXPECT_EQ(determineMatcherUpdateStatus(newConfig, 1, oldAtomMatchingTrackerMap,
                                            oldAtomMatchingTrackers, newAtomMatchingTrackerMap,
@@ -452,19 +477,13 @@
     EXPECT_NE(oldAtomMatchingTrackers[oldAtomMatchingTrackerMap.at(combination2Id)],
               newAtomMatchingTrackers[newAtomMatchingTrackerMap.at(combination2Id)]);
 
-    // Validation, make sure the matchers have the proper ids/indices. Could do more checks here.
+    // Validation, make sure the matchers have the proper ids. Could do more checks here.
     EXPECT_EQ(newAtomMatchingTrackers[0]->getId(), combination3Id);
-    EXPECT_EQ(newAtomMatchingTrackers[0]->mIndex, 0);
     EXPECT_EQ(newAtomMatchingTrackers[1]->getId(), simple2Id);
-    EXPECT_EQ(newAtomMatchingTrackers[1]->mIndex, 1);
     EXPECT_EQ(newAtomMatchingTrackers[2]->getId(), combination2Id);
-    EXPECT_EQ(newAtomMatchingTrackers[2]->mIndex, 2);
     EXPECT_EQ(newAtomMatchingTrackers[3]->getId(), simple1Id);
-    EXPECT_EQ(newAtomMatchingTrackers[3]->mIndex, 3);
     EXPECT_EQ(newAtomMatchingTrackers[4]->getId(), simple4Id);
-    EXPECT_EQ(newAtomMatchingTrackers[4]->mIndex, 4);
     EXPECT_EQ(newAtomMatchingTrackers[5]->getId(), combination1Id);
-    EXPECT_EQ(newAtomMatchingTrackers[5]->mIndex, 5);
 
     // Verify child indices of Combination Matchers are correct.
     CombinationAtomMatchingTracker* combinationTracker1 =
@@ -508,7 +527,7 @@
 
     set<int64_t> replacedMatchers;
     vector<UpdateStatus> conditionsToUpdate(1, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(1, false);
+    vector<uint8_t> cycleTracker(1, false);
     unordered_map<int64_t, int> newConditionTrackerMap;
     newConditionTrackerMap[predicate.id()] = 0;
     EXPECT_EQ(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap,
@@ -535,7 +554,7 @@
 
     set<int64_t> replacedMatchers;
     vector<UpdateStatus> conditionsToUpdate(1, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(1, false);
+    vector<uint8_t> cycleTracker(1, false);
     unordered_map<int64_t, int> newConditionTrackerMap;
     newConditionTrackerMap[predicate.id()] = 0;
     EXPECT_EQ(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap,
@@ -563,7 +582,7 @@
     replacedMatchers.insert(startMatcherId);
 
     vector<UpdateStatus> conditionsToUpdate(1, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(1, false);
+    vector<uint8_t> cycleTracker(1, false);
     unordered_map<int64_t, int> newConditionTrackerMap;
     newConditionTrackerMap[predicate.id()] = 0;
     EXPECT_EQ(determineConditionUpdateStatus(config, 0, oldConditionTrackerMap,
@@ -607,7 +626,7 @@
 
     set<int64_t> replacedMatchers;
     vector<UpdateStatus> conditionsToUpdate(3, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(3, false);
+    vector<uint8_t> cycleTracker(3, false);
     // Only update the combination. It should recurse the two child predicates and preserve all 3.
     EXPECT_EQ(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap,
                                              oldConditionTrackers, newConditionTrackerMap,
@@ -654,7 +673,7 @@
 
     set<int64_t> replacedMatchers;
     vector<UpdateStatus> conditionsToUpdate(3, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(3, false);
+    vector<uint8_t> cycleTracker(3, false);
     // Only update the combination. The simple conditions should not be evaluated.
     EXPECT_EQ(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap,
                                              oldConditionTrackers, newConditionTrackerMap,
@@ -700,7 +719,7 @@
 
     set<int64_t> replacedMatchers;
     vector<UpdateStatus> conditionsToUpdate(3, UPDATE_UNKNOWN);
-    vector<bool> cycleTracker(3, false);
+    vector<uint8_t> cycleTracker(3, false);
     // Only update the combination. Simple2 and combination1 must be evaluated.
     EXPECT_EQ(determineConditionUpdateStatus(newConfig, 0, oldConditionTrackerMap,
                                              oldConditionTrackers, newConditionTrackerMap,
@@ -792,7 +811,7 @@
     vector<MatchingState> eventMatcherValues(6, MatchingState::kNotMatched);
     eventMatcherValues[1] = MatchingState::kMatched;
     vector<ConditionState> tmpConditionCache(6, ConditionState::kNotEvaluated);
-    vector<bool> conditionChangeCache(6, false);
+    vector<uint8_t> conditionChangeCache(6, false);
     oldConditionTrackers[0]->evaluateCondition(event, eventMatcherValues, oldConditionTrackers,
                                                tmpConditionCache, conditionChangeCache);
     EXPECT_EQ(tmpConditionCache[0], ConditionState::kFalse);
@@ -1957,16 +1976,17 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, replacedConditions, newConditionTrackers,
                             conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
                             /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
-                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
-                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
-                            deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                            replacedMetrics),
+                            provider, newMetricProducerMap, newMetricProducers,
+                            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+                            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                            metricsWithActivation, replacedMetrics),
               nullopt);
 
     unordered_map<int64_t, int> expectedMetricProducerMap = {
@@ -2190,14 +2210,15 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, /*replacedConditions=*/{}, newConditionTrackers,
                             conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
-                            oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
-                            newMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                            noReportMetricIds, activationAtomTrackerToMetricMap,
+                            oldMetricProducerMap, oldMetricProducers, provider,
+                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
                             replacedMetrics),
               nullopt);
@@ -2403,16 +2424,17 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, /*replacedConditions=*/{}, newConditionTrackers,
                             conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
                             /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
-                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
-                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
-                            deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                            replacedMetrics),
+                            provider, newMetricProducerMap, newMetricProducers,
+                            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+                            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                            metricsWithActivation, replacedMetrics),
               nullopt);
 
     unordered_map<int64_t, int> expectedMetricProducerMap = {
@@ -2607,7 +2629,7 @@
     vector<MatchingState> matchingStates(8, MatchingState::kNotMatched);
     matchingStates[2] = kMatched;
     vector<ConditionState> conditionCache(5, ConditionState::kNotEvaluated);
-    vector<bool> changedCache(5, false);
+    vector<uint8_t> changedCache(5, false);
     unique_ptr<LogEvent> event = CreateAcquireWakelockEvent(timeBaseNs + 3, {uid1}, {"tag"}, "wl1");
     oldConditionTrackers[4]->evaluateCondition(*event.get(), matchingStates, oldConditionTrackers,
                                                conditionCache, changedCache);
@@ -2683,7 +2705,7 @@
                                                            newConditionTrackerMap),
                   nullopt);
     }
-    vector<bool> cycleTracker(5, false);
+    vector<uint8_t> cycleTracker(5, false);
     fill(conditionCache.begin(), conditionCache.end(), ConditionState::kNotEvaluated);
     for (int i = 0; i < newConditionTrackers.size(); i++) {
         EXPECT_EQ(
@@ -2726,12 +2748,13 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, /*replacedMatchers=*/{},
                             newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions,
                             newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps,
-                            replacedStates, oldMetricProducerMap, oldMetricProducers,
+                            replacedStates, oldMetricProducerMap, oldMetricProducers, provider,
                             newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                             trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
@@ -2994,12 +3017,13 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, /*replacedMatchers=*/{},
                             newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions,
                             newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps,
-                            replacedStates, oldMetricProducerMap, oldMetricProducers,
+                            replacedStates, oldMetricProducerMap, oldMetricProducers, provider,
                             newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                             trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
@@ -3211,13 +3235,14 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(
                       key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                       new StatsPullerManager(), oldAtomMatchingTrackerMap,
                       newAtomMatchingTrackerMap, /*replacedMatchers=*/{}, newAtomMatchingTrackers,
                       newConditionTrackerMap, replacedConditions, newConditionTrackers,
                       conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
-                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
+                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, provider,
                       newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                       trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                       deactivationAtomTrackerToMetricMap, metricsWithActivation, replacedMetrics),
@@ -3384,16 +3409,17 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, replacedConditions, newConditionTrackers,
                             conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
                             /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
-                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
-                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
-                            deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                            replacedMetrics),
+                            provider, newMetricProducerMap, newMetricProducers,
+                            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+                            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                            metricsWithActivation, replacedMetrics),
               nullopt);
 
     // Verify event activation/deactivation maps.
@@ -3546,16 +3572,17 @@
     unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, /*replacedConditions=*/{}, newConditionTrackers,
                             conditionCache, /*stateAtomIdMap*/ {}, /*allStateGroupMaps=*/{},
                             /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
-                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
-                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
-                            deactivationAtomTrackerToMetricMap, metricsWithActivation,
-                            replacedMetrics),
+                            provider, newMetricProducerMap, newMetricProducers,
+                            conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+                            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                            metricsWithActivation, replacedMetrics),
               nullopt);
 
     unordered_map<int64_t, int> expectedMetricProducerMap = {
@@ -3777,13 +3804,14 @@
     vector<int> metricsWithActivation;
     set<int64_t> replacedMetrics;
     int64_t currentTimeNs = 12345;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(
                       key, config, /*timeBaseNs=*/123, currentTimeNs, new StatsPullerManager(),
                       oldAtomMatchingTrackerMap, oldAtomMatchingTrackerMap, /*replacedMatchers*/ {},
                       oldAtomMatchingTrackers, oldConditionTrackerMap, /*replacedConditions=*/{},
                       oldConditionTrackers, {ConditionState::kUnknown}, /*stateAtomIdMap*/ {},
                       /*allStateGroupMaps=*/{},
-                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers,
+                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, provider,
                       newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                       trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                       deactivationAtomTrackerToMetricMap, metricsWithActivation, replacedMetrics),
@@ -3966,14 +3994,15 @@
     set<int64_t> replacedMatchers;
     set<int64_t> replacedConditions;
     set<int64_t> replacedStates;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, replacedConditions, newConditionTrackers,
                             conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
-                            oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
-                            newMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                            noReportMetricIds, activationAtomTrackerToMetricMap,
+                            oldMetricProducerMap, oldMetricProducers, provider,
+                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
                             replacedMetrics),
               InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_HAS_MULTIPLE_ACTIVATIONS, metricId));
@@ -4003,14 +4032,15 @@
     set<int64_t> replacedMatchers;
     set<int64_t> replacedConditions;
     set<int64_t> replacedStates;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     EXPECT_EQ(updateMetrics(key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
                             new StatsPullerManager(), oldAtomMatchingTrackerMap,
                             newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
                             newConditionTrackerMap, replacedConditions, newConditionTrackers,
                             conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
-                            oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
-                            newMetricProducers, conditionToMetricMap, trackerToMetricMap,
-                            noReportMetricIds, activationAtomTrackerToMetricMap,
+                            oldMetricProducerMap, oldMetricProducers, provider,
+                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                             deactivationAtomTrackerToMetricMap, metricsWithActivation,
                             replacedMetrics),
               InvalidConfigReason(INVALID_CONFIG_REASON_NO_REPORT_METRIC_NOT_FOUND, metricId));
@@ -4047,6 +4077,7 @@
     set<int64_t> replacedMatchers;
     set<int64_t> replacedConditions;
     set<int64_t> replacedStates;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
 
     newAtomMatchingTrackerMap[StringToId("ScreenTurnedOn")] = 0;
     stateAtomIdMap[StringToId("ScreenState")] = util::SCREEN_STATE_CHANGED;
@@ -4057,7 +4088,7 @@
                     replacedMatchers, newAtomMatchingTrackers, newConditionTrackerMap,
                     replacedConditions, newConditionTrackers, conditionCache, stateAtomIdMap,
                     allStateGroupMaps, replacedStates, oldMetricProducerMap, oldMetricProducers,
-                    newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                    provider, newMetricProducerMap, newMetricProducers, conditionToMetricMap,
                     trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
                     deactivationAtomTrackerToMetricMap, metricsWithActivation, replacedMetrics),
             InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_SLICED_STATE_ATOM_ALLOWED_FROM_ANY_UID,
@@ -4104,6 +4135,149 @@
                                                      StringToId("ScreenIsOn")));
 }
 
+TEST_F(ConfigUpdateTest, TestUpdateConfigNonEventMetricHasRestrictedDelegate) {
+    StatsdConfig config;
+    CountMetric* metric = config.add_count_metric();
+    config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+    unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
+    unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+    unordered_map<int64_t, int> newConditionTrackerMap;
+    unordered_map<int64_t, int> newMetricProducerMap;
+    unordered_map<int64_t, int> stateAtomIdMap;
+    unordered_map<int, vector<int>> conditionToMetricMap;
+    unordered_map<int, vector<int>> trackerToMetricMap;
+    unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+    set<int64_t> noReportMetricIds;
+    vector<int> metricsWithActivation;
+    vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers;
+    vector<sp<ConditionTracker>> newConditionTrackers;
+    vector<sp<MetricProducer>> newMetricProducers;
+    vector<ConditionState> conditionCache;
+    set<int64_t> replacedMetrics;
+    set<int64_t> replacedMatchers;
+    set<int64_t> replacedConditions;
+    set<int64_t> replacedStates;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+
+    EXPECT_EQ(updateMetrics(key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
+                            new StatsPullerManager(), oldAtomMatchingTrackerMap,
+                            newAtomMatchingTrackerMap, replacedMatchers, newAtomMatchingTrackers,
+                            newConditionTrackerMap, replacedConditions, newConditionTrackers,
+                            conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
+                            oldMetricProducerMap, oldMetricProducers, provider,
+                            newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                            trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
+                            deactivationAtomTrackerToMetricMap, metricsWithActivation,
+                            replacedMetrics),
+              InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
+TEST_P(ConfigUpdateDimLimitTest, TestDimLimit) {
+    StatsdConfig config = buildGoodConfig(configId);
+    const auto& [oldLimit, newLimit, actualLimit] = GetParam();
+    if (oldLimit > 0) {
+        config.mutable_count_metric(0)->set_max_dimensions_per_bucket(oldLimit);
+        config.mutable_duration_metric(0)->set_max_dimensions_per_bucket(oldLimit);
+        config.mutable_gauge_metric(0)->set_max_dimensions_per_bucket(oldLimit);
+        config.mutable_value_metric(0)->set_max_dimensions_per_bucket(oldLimit);
+        config.mutable_kll_metric(0)->set_max_dimensions_per_bucket(oldLimit);
+    }
+
+    EXPECT_TRUE(initConfig(config));
+
+    StatsdConfig newConfig = config;
+    if (newLimit == 0) {
+        newConfig.mutable_count_metric(0)->clear_max_dimensions_per_bucket();
+        newConfig.mutable_duration_metric(0)->clear_max_dimensions_per_bucket();
+        newConfig.mutable_gauge_metric(0)->clear_max_dimensions_per_bucket();
+        newConfig.mutable_value_metric(0)->clear_max_dimensions_per_bucket();
+        newConfig.mutable_kll_metric(0)->clear_max_dimensions_per_bucket();
+    } else {
+        newConfig.mutable_count_metric(0)->set_max_dimensions_per_bucket(newLimit);
+        newConfig.mutable_duration_metric(0)->set_max_dimensions_per_bucket(newLimit);
+        newConfig.mutable_gauge_metric(0)->set_max_dimensions_per_bucket(newLimit);
+        newConfig.mutable_value_metric(0)->set_max_dimensions_per_bucket(newLimit);
+        newConfig.mutable_kll_metric(0)->set_max_dimensions_per_bucket(newLimit);
+    }
+
+    unordered_map<int64_t, int> newMetricProducerMap;
+    unordered_map<int, vector<int>> conditionToMetricMap;
+    unordered_map<int, vector<int>> trackerToMetricMap;
+    unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+    set<int64_t> noReportMetricIds;
+    vector<int> metricsWithActivation;
+    vector<sp<MetricProducer>> newMetricProducers;
+    set<int64_t> replacedMetrics;
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    EXPECT_EQ(updateMetrics(
+                      key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345,
+                      new StatsPullerManager(), oldAtomMatchingTrackerMap,
+                      oldAtomMatchingTrackerMap, /*replacedMatchers=*/{}, oldAtomMatchingTrackers,
+                      oldConditionTrackerMap, /*replacedConditions=*/{}, oldConditionTrackers,
+                      /*conditionCache=*/{}, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
+                      /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, provider,
+                      newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+                      trackerToMetricMap, noReportMetricIds, activationAtomTrackerToMetricMap,
+                      deactivationAtomTrackerToMetricMap, metricsWithActivation, replacedMetrics),
+              nullopt);
+
+    ASSERT_EQ(5u, oldMetricProducers.size());
+    ASSERT_EQ(5u, newMetricProducers.size());
+
+    // Check that old MetricProducers have the old dimension limit and the new producers have the
+    // new dimension limit.
+
+    // Count
+    sp<MetricProducer> producer =
+            oldMetricProducers[oldMetricProducerMap.at(config.count_metric(0).id())];
+    CountMetricProducer* countProducer = static_cast<CountMetricProducer*>(producer.get());
+    EXPECT_EQ(countProducer->mDimensionHardLimit, oldLimit);
+
+    producer = newMetricProducers[newMetricProducerMap.at(newConfig.count_metric(0).id())];
+    countProducer = static_cast<CountMetricProducer*>(producer.get());
+    EXPECT_EQ(countProducer->mDimensionHardLimit, actualLimit);
+
+    // Duration
+    producer = oldMetricProducers[oldMetricProducerMap.at(config.duration_metric(0).id())];
+    DurationMetricProducer* durationProducer = static_cast<DurationMetricProducer*>(producer.get());
+    EXPECT_EQ(durationProducer->mDimensionHardLimit, oldLimit);
+
+    producer = newMetricProducers[newMetricProducerMap.at(newConfig.duration_metric(0).id())];
+    durationProducer = static_cast<DurationMetricProducer*>(producer.get());
+    EXPECT_EQ(durationProducer->mDimensionHardLimit, actualLimit);
+
+    // Gauge
+    producer = oldMetricProducers[oldMetricProducerMap.at(config.gauge_metric(0).id())];
+    GaugeMetricProducer* gaugeProducer = static_cast<GaugeMetricProducer*>(producer.get());
+    EXPECT_EQ(gaugeProducer->mDimensionHardLimit, oldLimit);
+
+    producer = newMetricProducers[newMetricProducerMap.at(newConfig.gauge_metric(0).id())];
+    gaugeProducer = static_cast<GaugeMetricProducer*>(producer.get());
+    EXPECT_EQ(gaugeProducer->mDimensionHardLimit, actualLimit);
+
+    // Value
+    producer = oldMetricProducers[oldMetricProducerMap.at(config.value_metric(0).id())];
+    NumericValueMetricProducer* numericValueProducer =
+            static_cast<NumericValueMetricProducer*>(producer.get());
+    EXPECT_EQ(numericValueProducer->mDimensionHardLimit, oldLimit);
+
+    producer = newMetricProducers[newMetricProducerMap.at(newConfig.value_metric(0).id())];
+    numericValueProducer = static_cast<NumericValueMetricProducer*>(producer.get());
+    EXPECT_EQ(numericValueProducer->mDimensionHardLimit, actualLimit);
+
+    // KLL
+    producer = oldMetricProducers[oldMetricProducerMap.at(config.kll_metric(0).id())];
+    KllMetricProducer* kllProducer = static_cast<KllMetricProducer*>(producer.get());
+    EXPECT_EQ(kllProducer->mDimensionHardLimit, oldLimit);
+
+    producer = newMetricProducers[newMetricProducerMap.at(newConfig.kll_metric(0).id())];
+    kllProducer = static_cast<KllMetricProducer*>(producer.get());
+    EXPECT_EQ(kllProducer->mDimensionHardLimit, actualLimit);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
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 2d010a3..1a64c20 100644
--- a/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
+++ b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -28,6 +28,7 @@
 #include "src/metrics/CountMetricProducer.h"
 #include "src/metrics/DurationMetricProducer.h"
 #include "src/metrics/GaugeMetricProducer.h"
+#include "src/metrics/KllMetricProducer.h"
 #include "src/metrics/MetricProducer.h"
 #include "src/metrics/NumericValueMetricProducer.h"
 #include "src/state/StateManager.h"
@@ -50,7 +51,8 @@
 namespace statsd {
 
 namespace {
-const ConfigKey kConfigKey(0, 12345);
+const int kConfigId = 12345;
+const ConfigKey kConfigKey(0, kConfigId);
 const long timeBaseSec = 1000;
 const long kAlertId = 3;
 
@@ -58,6 +60,7 @@
 sp<StatsPullerManager> pullerManager = new StatsPullerManager();
 sp<AlarmMonitor> anomalyAlarmMonitor;
 sp<AlarmMonitor> periodicAlarmMonitor;
+sp<ConfigMetadataProvider> configMetadataProvider;
 unordered_map<int, vector<int>> allTagIdsToMatchersMap;
 vector<sp<AtomMatchingTracker>> allAtomMatchingTrackers;
 unordered_map<int64_t, int> atomMatchingTrackerMap;
@@ -81,62 +84,12 @@
     // initStatsdConfig returns nullopt if config is valid
     return initStatsdConfig(
             kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-            timeBaseSec, timeBaseSec, allTagIdsToMatchersMap, allAtomMatchingTrackers,
-            atomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap, allMetricProducers,
-            metricProducerMap, allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
-            trackerToMetricMap, trackerToConditionMap, activationAtomTrackerToMetricMap,
-            deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation,
-            stateProtoHashes, noReportMetricIds);
-}
-
-StatsdConfig buildGoodConfig() {
-    StatsdConfig config;
-    config.set_id(12345);
-
-    AtomMatcher* eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_ON"));
-
-    SimpleAtomMatcher* simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(SCREEN_STATE_ATOM_ID);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_IS_OFF"));
-
-    simpleAtomMatcher = eventMatcher->mutable_simple_atom_matcher();
-    simpleAtomMatcher->set_atom_id(SCREEN_STATE_ATOM_ID);
-    simpleAtomMatcher->add_field_value_matcher()->set_field(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
-    simpleAtomMatcher->mutable_field_value_matcher(0)->set_eq_int(
-            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
-
-    eventMatcher = config.add_atom_matcher();
-    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
-
-    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
-    combination->set_operation(LogicalOperation::OR);
-    combination->add_matcher(StringToId("SCREEN_IS_ON"));
-    combination->add_matcher(StringToId("SCREEN_IS_OFF"));
-
-    CountMetric* metric = config.add_count_metric();
-    metric->set_id(3);
-    metric->set_what(StringToId("SCREEN_IS_ON"));
-    metric->set_bucket(ONE_MINUTE);
-    metric->mutable_dimensions_in_what()->set_field(SCREEN_STATE_ATOM_ID);
-    metric->mutable_dimensions_in_what()->add_child()->set_field(1);
-
-    config.add_no_report_metric(3);
-
-    auto alert = config.add_alert();
-    alert->set_id(kAlertId);
-    alert->set_metric_id(3);
-    alert->set_num_buckets(10);
-    alert->set_refractory_period_secs(100);
-    alert->set_trigger_if_sum_gt(100);
-    return config;
+            timeBaseSec, timeBaseSec, configMetadataProvider, allTagIdsToMatchersMap,
+            allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers,
+            conditionTrackerMap, allMetricProducers, metricProducerMap, allAnomalyTrackers,
+            allAlarmTrackers, conditionToMetricMap, trackerToMetricMap, trackerToConditionMap,
+            activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, alertTrackerMap,
+            metricsWithActivation, stateProtoHashes, noReportMetricIds);
 }
 
 StatsdConfig buildCircleMatchers() {
@@ -409,7 +362,6 @@
 
     return config;
 }
-}  // anonymous namespace
 
 class MetricsManagerUtilTest : public ::testing::Test {
 public:
@@ -436,6 +388,25 @@
     }
 };
 
+struct DimLimitTestCase {
+    int configLimit;
+    int actualLimit;
+
+    friend void PrintTo(const DimLimitTestCase& testCase, ostream* os) {
+        *os << testCase.configLimit;
+    }
+};
+
+class MetricsManagerUtilDimLimitTest : public MetricsManagerUtilTest,
+                                       public WithParamInterface<DimLimitTestCase> {};
+
+const vector<DimLimitTestCase> dimLimitTestCases = {{900, 900}, {799, 800}, {3001, 3000}, {0, 800}};
+
+INSTANTIATE_TEST_SUITE_P(DimLimit, MetricsManagerUtilDimLimitTest, ValuesIn(dimLimitTestCases),
+                         PrintToStringParamName());
+
+}  // anonymous namespace
+
 TEST_F(MetricsManagerUtilTest, TestInitialConditions) {
     // initConfig returns nullopt if config is valid
     EXPECT_EQ(initConfig(buildConfigWithDifferentPredicates()), nullopt);
@@ -465,11 +436,15 @@
 }
 
 TEST_F(MetricsManagerUtilTest, TestGoodConfig) {
-    StatsdConfig config = buildGoodConfig();
+    StatsdConfig config = buildGoodConfig(kConfigId, kAlertId);
     // initConfig returns nullopt if config is valid
     EXPECT_EQ(initConfig(config), nullopt);
-    ASSERT_EQ(1u, allMetricProducers.size());
-    EXPECT_THAT(metricProducerMap, UnorderedElementsAre(Pair(config.count_metric(0).id(), 0)));
+    ASSERT_EQ(5u, allMetricProducers.size());
+    EXPECT_THAT(metricProducerMap, UnorderedElementsAre(Key(config.count_metric(0).id()),
+                                                        Key(config.duration_metric(0).id()),
+                                                        Key(config.value_metric(0).id()),
+                                                        Key(config.kll_metric(0).id()),
+                                                        Key(config.gauge_metric(0).id())));
     ASSERT_EQ(1u, allAnomalyTrackers.size());
     ASSERT_EQ(1u, noReportMetricIds.size());
     ASSERT_EQ(1u, alertTrackerMap.size());
@@ -641,6 +616,171 @@
                                   StringToId("Event")));
 }
 
+TEST_F(MetricsManagerUtilTest, TestEventMetricInvalidSamplingPercentage) {
+    StatsdConfig config;
+    EventMetric* metric = config.add_event_metric();
+    *metric = createEventMetric(/*name=*/"Event", /*what=*/StringToId("ScreenTurnedOn"),
+                                /*condition=*/nullopt);
+    metric->set_sampling_percentage(101);
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_INCORRECT_SAMPLING_PERCENTAGE,
+                                  StringToId("Event")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestEventMetricInvalidSamplingPercentageZero) {
+    StatsdConfig config;
+    EventMetric* metric = config.add_event_metric();
+    *metric = createEventMetric(/*name=*/"Event", /*what=*/StringToId("ScreenTurnedOn"),
+                                /*condition=*/nullopt);
+    metric->set_sampling_percentage(0);
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_INCORRECT_SAMPLING_PERCENTAGE,
+                                  StringToId("Event")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestEventMetricValidSamplingPercentage) {
+    StatsdConfig config;
+    EventMetric* metric = config.add_event_metric();
+    *metric = createEventMetric(/*name=*/"Event", /*what=*/StringToId("ScreenTurnedOn"),
+                                /*condition=*/nullopt);
+    metric->set_sampling_percentage(50);
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricInvalidSamplingPercentage) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("ScreenTurnedOn"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_sampling_percentage(101);
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_INCORRECT_SAMPLING_PERCENTAGE,
+                                  StringToId("Gauge")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricInvalidSamplingPercentageZero) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("ScreenTurnedOn"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_sampling_percentage(0);
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_INCORRECT_SAMPLING_PERCENTAGE,
+                                  StringToId("Gauge")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricValidSamplingPercentage) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("ScreenTurnedOn"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_sampling_percentage(50);
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestPulledGaugeMetricWithSamplingPercentage) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("SubsystemSleep"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_sampling_percentage(50);
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_GAUGE_METRIC_PULLED_WITH_SAMPLING,
+                                  StringToId("Gauge")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricInvalidPullProbability) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("SubsystemSleep"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_pull_probability(101);
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_INCORRECT_PULL_PROBABILITY,
+                                  StringToId("Gauge")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricInvalidPullProbabilityZero) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("SubsystemSleep"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_pull_probability(0);
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_INCORRECT_PULL_PROBABILITY,
+                                  StringToId("Gauge")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricValidPullProbability) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("SubsystemSleep"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_pull_probability(50);
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestPushedGaugeMetricWithPullProbability) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("ScreenTurnedOn"),
+                                GaugeMetric::FIRST_N_SAMPLES,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_pull_probability(50);
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_GAUGE_METRIC_PUSHED_WITH_PULL_PROBABILITY,
+                                  StringToId("Gauge")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricRandomOneSampleWithPullProbability) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    *metric = createGaugeMetric(/*name=*/"Gauge", /*what=*/StringToId("SubsystemSleep"),
+                                GaugeMetric::RANDOM_ONE_SAMPLE,
+                                /*condition=*/nullopt, /*triggerEvent=*/nullopt);
+    metric->set_pull_probability(50);
+    *config.add_atom_matcher() =
+            CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(
+                      INVALID_CONFIG_REASON_GAUGE_METRIC_RANDOM_ONE_SAMPLE_WITH_PULL_PROBABILITY,
+                      StringToId("Gauge")));
+}
+
 TEST_F(MetricsManagerUtilTest, TestNumericValueMetricMissingIdOrWhat) {
     StatsdConfig config;
     int64_t metricId = 1;
@@ -666,6 +806,88 @@
                                   StringToId("NumericValue")));
 }
 
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricHasBothSingleAndMultipleAggTypes) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    ValueMetric* metric = config.add_value_metric();
+    *metric = createValueMetric(/*name=*/"NumericValue", /*what=*/CreateScreenTurnedOnAtomMatcher(),
+                                /*valueField=*/2, /*condition=*/nullopt, /*states=*/{});
+    metric->set_aggregation_type(ValueMetric::SUM);
+    metric->add_aggregation_types(ValueMetric::SUM);
+    metric->add_aggregation_types(ValueMetric::MIN);
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(
+                      INVALID_CONFIG_REASON_VALUE_METRIC_DEFINES_SINGLE_AND_MULTIPLE_AGG_TYPES,
+                      StringToId("NumericValue")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricMoreAggTypesThanValueFields) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    ValueMetric* metric = config.add_value_metric();
+    *metric = createValueMetric(/*name=*/"NumericValue", /*what=*/CreateScreenTurnedOnAtomMatcher(),
+                                /*valueField=*/2, /*condition=*/nullopt, /*states=*/{});
+    metric->add_aggregation_types(ValueMetric::SUM);
+    metric->add_aggregation_types(ValueMetric::MIN);
+
+    EXPECT_EQ(
+            initConfig(config),
+            InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_AGG_TYPES_DNE_VALUE_FIELDS_SIZE,
+                                StringToId("NumericValue")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricMoreValueFieldsThanAggTypes) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    ValueMetric* metric = config.add_value_metric();
+    *metric = createValueMetric(/*name=*/"NumericValue", /*what=*/CreateScreenTurnedOnAtomMatcher(),
+                                /*valueField=*/2, /*condition=*/nullopt, /*states=*/{});
+    // This only fails if the repeated aggregation field is used. If the single field is used,
+    // we will apply this aggregation type to all value fields.
+    metric->add_aggregation_types(ValueMetric::SUM);
+    metric->add_aggregation_types(ValueMetric::MIN);
+    *metric->mutable_value_field() = CreateDimensions(
+            util::SUBSYSTEM_SLEEP_STATE, {3 /* count */, 4 /* time_millis */, 3 /* count */});
+
+    EXPECT_EQ(
+            initConfig(config),
+            InvalidConfigReason(INVALID_CONFIG_REASON_VALUE_METRIC_AGG_TYPES_DNE_VALUE_FIELDS_SIZE,
+                                StringToId("NumericValue")));
+}
+
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricDefaultAggTypeOutOfOrderFields) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    ValueMetric* metric = config.add_value_metric();
+    *metric = createValueMetric(/*name=*/"NumericValue", /*what=*/CreateScreenTurnedOnAtomMatcher(),
+                                /*valueField=*/2, /*condition=*/nullopt, /*states=*/{});
+    *metric->mutable_value_field() =
+            CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time_millis */, 3 /* count */});
+
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricMultipleAggTypesOutOfOrderFields) {
+    StatsdConfig config;
+    *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+
+    ValueMetric* metric = config.add_value_metric();
+    *metric = createValueMetric(/*name=*/"NumericValue", /*what=*/CreateScreenTurnedOnAtomMatcher(),
+                                /*valueField=*/2, /*condition=*/nullopt, /*states=*/{});
+    metric->add_aggregation_types(ValueMetric::SUM);
+    metric->add_aggregation_types(ValueMetric::MIN);
+    metric->add_aggregation_types(ValueMetric::SUM);
+    *metric->mutable_value_field() = CreateDimensions(
+            util::SUBSYSTEM_SLEEP_STATE, {3 /* count */, 4 /* time_millis */, 3 /* count */});
+
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
 TEST_F(MetricsManagerUtilTest, TestKllMetricMissingIdOrWhat) {
     StatsdConfig config;
     int64_t metricId = 1;
@@ -1163,14 +1385,13 @@
     // Matcher has no contents_case (simple/combination), so it is invalid.
     matcher.set_id(21);
     optional<InvalidConfigReason> invalidConfigReason;
-    EXPECT_EQ(createAtomMatchingTracker(matcher, 0, uidMap, invalidConfigReason), nullptr);
+    EXPECT_EQ(createAtomMatchingTracker(matcher, uidMap, invalidConfigReason), nullptr);
     EXPECT_EQ(invalidConfigReason,
               createInvalidConfigReasonWithMatcher(
                       INVALID_CONFIG_REASON_MATCHER_MALFORMED_CONTENTS_CASE, matcher.id()));
 }
 
 TEST_F(MetricsManagerUtilTest, TestCreateAtomMatchingTrackerSimple) {
-    int index = 1;
     int64_t id = 123;
     sp<UidMap> uidMap = new UidMap();
     AtomMatcher matcher;
@@ -1184,20 +1405,18 @@
 
     optional<InvalidConfigReason> invalidConfigReason;
     sp<AtomMatchingTracker> tracker =
-            createAtomMatchingTracker(matcher, index, uidMap, invalidConfigReason);
+            createAtomMatchingTracker(matcher, uidMap, invalidConfigReason);
     EXPECT_NE(tracker, nullptr);
     EXPECT_EQ(invalidConfigReason, nullopt);
 
     EXPECT_TRUE(tracker->mInitialized);
     EXPECT_EQ(tracker->getId(), id);
-    EXPECT_EQ(tracker->mIndex, index);
     const set<int>& atomIds = tracker->getAtomIds();
     ASSERT_EQ(atomIds.size(), 1);
     EXPECT_EQ(atomIds.count(SCREEN_STATE_ATOM_ID), 1);
 }
 
 TEST_F(MetricsManagerUtilTest, TestCreateAtomMatchingTrackerCombination) {
-    int index = 1;
     int64_t id = 123;
     sp<UidMap> uidMap = new UidMap();
     AtomMatcher matcher;
@@ -1209,14 +1428,13 @@
 
     optional<InvalidConfigReason> invalidConfigReason;
     sp<AtomMatchingTracker> tracker =
-            createAtomMatchingTracker(matcher, index, uidMap, invalidConfigReason);
+            createAtomMatchingTracker(matcher, uidMap, invalidConfigReason);
     EXPECT_NE(tracker, nullptr);
     EXPECT_EQ(invalidConfigReason, nullopt);
 
     // Combination matchers need to be initialized first.
     EXPECT_FALSE(tracker->mInitialized);
     EXPECT_EQ(tracker->getId(), id);
-    EXPECT_EQ(tracker->mIndex, index);
     const set<int>& atomIds = tracker->getAtomIds();
     ASSERT_EQ(atomIds.size(), 0);
 }
@@ -1321,8 +1539,10 @@
     metric.set_id(metricId);
     metric.set_bucket(ONE_MINUTE);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    vector<sp<MetricProducer>> metricProducers({new CountMetricProducer(
-            kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)});
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    vector<sp<MetricProducer>> metricProducers(
+            {new CountMetricProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
+                                     0x0123456789, 0, 0, provider)});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     optional<InvalidConfigReason> invalidConfigReason;
     EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123,
@@ -1344,8 +1564,10 @@
     metric.set_id(metricId);
     metric.set_bucket(ONE_MINUTE);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    vector<sp<MetricProducer>> metricProducers({new CountMetricProducer(
-            kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)});
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    vector<sp<MetricProducer>> metricProducers(
+            {new CountMetricProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
+                                     0x0123456789, 0, 0, provider)});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     optional<InvalidConfigReason> invalidConfigReason;
     EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123,
@@ -1368,8 +1590,10 @@
     metric.set_id(metricId);
     metric.set_bucket(ONE_MINUTE);
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
-    vector<sp<MetricProducer>> metricProducers({new CountMetricProducer(
-            kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard, 0x0123456789, 0, 0)});
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
+    vector<sp<MetricProducer>> metricProducers(
+            {new CountMetricProducer(kConfigKey, metric, 0, {ConditionState::kUnknown}, wizard,
+                                     0x0123456789, 0, 0, provider)});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     optional<InvalidConfigReason> invalidConfigReason;
     EXPECT_NE(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123,
@@ -1393,10 +1617,11 @@
     metric.set_aggregation_type(DurationMetric_AggregationType_SUM);
     FieldMatcher dimensions;
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockConfigMetadataProvider> provider = makeMockConfigMetadataProvider(/*enabled=*/false);
     vector<sp<MetricProducer>> metricProducers({new DurationMetricProducer(
             kConfigKey, metric, -1 /*no condition*/, {}, -1 /* what index not needed*/,
             1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/,
-            wizard, 0x0123456789, dimensions, 0, 0)});
+            wizard, 0x0123456789, dimensions, 0, 0, provider)});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     optional<InvalidConfigReason> invalidConfigReason;
     EXPECT_EQ(createAnomalyTracker(alert, anomalyAlarmMonitor, UPDATE_NEW, /*updateTime=*/123,
@@ -1409,7 +1634,6 @@
 
 TEST_F(MetricsManagerUtilTest, TestCreateDurationProducerDimensionsInWhatInvalid) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
     *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
@@ -1446,7 +1670,6 @@
 
 TEST_F(MetricsManagerUtilTest, TestSampledMetrics) {
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
 
     AtomMatcher appCrashMatcher =
             CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
@@ -1598,7 +1821,6 @@
             CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
 
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = appCrashMatcher;
 
     CountMetric metric =
@@ -1618,7 +1840,6 @@
             CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
 
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = appCrashMatcher;
 
     CountMetric metric =
@@ -1639,7 +1860,6 @@
             CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
 
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = appCrashMatcher;
 
     CountMetric metric =
@@ -1660,7 +1880,6 @@
             CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
 
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = testAtomReportedMatcher;
 
     CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
@@ -1678,54 +1897,11 @@
                                   metric.id()));
 }
 
-TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_PositionFIRST) {
-    AtomMatcher testAtomReportedMatcher =
-            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
-
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
-    *config.add_atom_matcher() = testAtomReportedMatcher;
-
-    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
-                                           testAtomReportedMatcher.id(), nullopt, {});
-    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
-            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::FIRST});
-    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
-            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
-                                     {Position::FIRST});
-    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
-    *config.add_count_metric() = metric;
-
-    EXPECT_EQ(initConfig(config), nullopt);
-}
-
-TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_PositionLAST) {
-    AtomMatcher testAtomReportedMatcher =
-            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
-
-    StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
-    *config.add_atom_matcher() = testAtomReportedMatcher;
-
-    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
-                                           testAtomReportedMatcher.id(), nullopt, {});
-    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
-            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::LAST});
-    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
-            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
-                                     {Position::LAST});
-    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
-    *config.add_count_metric() = metric;
-
-    EXPECT_EQ(initConfig(config), nullopt);
-}
-
 TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_PositionANY) {
     AtomMatcher testAtomReportedMatcher =
             CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
 
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = testAtomReportedMatcher;
 
     CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
@@ -1743,12 +1919,11 @@
                                   metric.id()));
 }
 
-TEST_F(MetricsManagerUtilTest, TestMetricSampledFieldNotSubsetDimension) {
+TEST_F(MetricsManagerUtilTest, TestMetricSampledField_DifferentFieldsNotSubsetDimension) {
     AtomMatcher appCrashMatcher =
             CreateSimpleAtomMatcher("APP_CRASH_OCCURRED", util::APP_CRASH_OCCURRED);
 
     StatsdConfig config;
-    config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = appCrashMatcher;
 
     CountMetric metric =
@@ -1764,6 +1939,522 @@
                                 metric.id()));
 }
 
+TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_LastNotSubsetDimensionsFirst) {
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
+
+    StatsdConfig config;
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
+                                           testAtomReportedMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::FIRST});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
+                                     {Position::LAST});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+
+    EXPECT_EQ(
+            initConfig(config),
+            InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELDS_NOT_SUBSET_DIM_IN_WHAT,
+                                metric.id()));
+}
+
+TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_FirstNotSubsetDimensionsLast) {
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
+
+    StatsdConfig config;
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
+                                           testAtomReportedMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::LAST});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
+                                     {Position::FIRST});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+
+    EXPECT_EQ(
+            initConfig(config),
+            InvalidConfigReason(INVALID_CONFIG_REASON_METRIC_SAMPLED_FIELDS_NOT_SUBSET_DIM_IN_WHAT,
+                                metric.id()));
+}
+
+// dimensions_in_what position ALL, sampled_what_field position FIRST
+TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_FirstSubsetDimensionsAll) {
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
+
+    StatsdConfig config;
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
+                                           testAtomReportedMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::ALL});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
+                                     {Position::FIRST});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+// dimensions_in_what position ALL, sampled_what_field position LAST
+TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_LastSubsetDimensionsAll) {
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
+
+    StatsdConfig config;
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
+                                           testAtomReportedMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::ALL});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
+                                     {Position::LAST});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+// dimensions_in_what position FIRST, sampled_what_field position FIRST
+TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_FirstSubsetDimensionsFirst) {
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
+
+    StatsdConfig config;
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
+                                           testAtomReportedMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::FIRST});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
+                                     {Position::FIRST});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+// dimensions_in_what position LAST, sampled_what_field position LAST
+TEST_F(MetricsManagerUtilTest, TestMetricHasRepeatedSampledField_LastSubsetDimensionsLast) {
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TEST_ATOM_REPORTED", util::TEST_ATOM_REPORTED);
+
+    StatsdConfig config;
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    CountMetric metric = createCountMetric("CountSampledTestAtomReportedPerRepeatedIntField",
+                                           testAtomReportedMatcher.id(), nullopt, {});
+    *metric.mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::LAST});
+    *metric.mutable_dimensional_sampling_info()->mutable_sampled_what_field() =
+            CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/},
+                                     {Position::LAST});
+    metric.mutable_dimensional_sampling_info()->set_shard_count(2);
+    *config.add_count_metric() = metric;
+    EXPECT_EQ(initConfig(config), nullopt);
+}
+
+TEST_F(MetricsManagerUtilTest, TestCountMetricHasRestrictedDelegate) {
+    StatsdConfig config;
+    CountMetric* metric = config.add_count_metric();
+    config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
+TEST_F(MetricsManagerUtilTest, TestDurationMetricHasRestrictedDelegate) {
+    StatsdConfig config;
+    DurationMetric* metric = config.add_duration_metric();
+    config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
+TEST_F(MetricsManagerUtilTest, TestGaugeMetricHasRestrictedDelegate) {
+    StatsdConfig config;
+    GaugeMetric* metric = config.add_gauge_metric();
+    config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
+TEST_F(MetricsManagerUtilTest, TestNumericValueMetricHasRestrictedDelegate) {
+    StatsdConfig config;
+    ValueMetric* metric = config.add_value_metric();
+    config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
+TEST_F(MetricsManagerUtilTest, TestKllMetricHasRestrictedDelegate) {
+    StatsdConfig config;
+    KllMetric* metric = config.add_kll_metric();
+    config.set_restricted_metrics_delegate_package_name("com.android.app.test");
+
+    EXPECT_EQ(initConfig(config),
+              InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
+TEST_P(MetricsManagerUtilDimLimitTest, TestDimLimit) {
+    StatsdConfig config = buildGoodConfig(kConfigId, kAlertId);
+    const auto& [configLimit, actualLimit] = GetParam();
+    if (configLimit > 0) {
+        config.mutable_count_metric(0)->set_max_dimensions_per_bucket(configLimit);
+        config.mutable_duration_metric(0)->set_max_dimensions_per_bucket(configLimit);
+        config.mutable_gauge_metric(0)->set_max_dimensions_per_bucket(configLimit);
+        config.mutable_value_metric(0)->set_max_dimensions_per_bucket(configLimit);
+        config.mutable_kll_metric(0)->set_max_dimensions_per_bucket(configLimit);
+    }
+
+    // initConfig returns nullopt if config is valid
+    EXPECT_EQ(initConfig(config), nullopt);
+    ASSERT_EQ(5u, allMetricProducers.size());
+
+    sp<MetricProducer> producer =
+            allMetricProducers[metricProducerMap.at(config.count_metric(0).id())];
+    CountMetricProducer* countProducer = static_cast<CountMetricProducer*>(producer.get());
+    EXPECT_EQ(countProducer->mDimensionHardLimit, actualLimit);
+
+    producer = allMetricProducers[metricProducerMap.at(config.duration_metric(0).id())];
+    DurationMetricProducer* durationProducer = static_cast<DurationMetricProducer*>(producer.get());
+    EXPECT_EQ(durationProducer->mDimensionHardLimit, actualLimit);
+
+    producer = allMetricProducers[metricProducerMap.at(config.gauge_metric(0).id())];
+    GaugeMetricProducer* gaugeProducer = static_cast<GaugeMetricProducer*>(producer.get());
+    EXPECT_EQ(gaugeProducer->mDimensionHardLimit, actualLimit);
+
+    producer = allMetricProducers[metricProducerMap.at(config.value_metric(0).id())];
+    NumericValueMetricProducer* numericValueProducer =
+            static_cast<NumericValueMetricProducer*>(producer.get());
+    EXPECT_EQ(numericValueProducer->mDimensionHardLimit, actualLimit);
+
+    producer = allMetricProducers[metricProducerMap.at(config.kll_metric(0).id())];
+    KllMetricProducer* kllProducer = static_cast<KllMetricProducer*>(producer.get());
+    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"([0-9]+$)");
+    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"([0-9]+$)");
+    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"([0-9]+$)");
+    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"([0-9]+$)");
+    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"([0-9]+$)");
+    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"([0-9]+$)");
+    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"([0-9]+$)");
+    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 "[0-9]+$" -> "".
+    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"([0-9]+$)");
+    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"([0-9]+$)");
+    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"([0-9]+$)");
+    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 3f40b38..bc4bd9c 100644
--- a/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -160,7 +160,7 @@
                   const vector<std::shared_ptr<LogEvent>>& pushedEvents,
                   const vector<ShellData>& expectedData, int numClients) {
     sp<ShellSubscriber> shellManager =
-            new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
+            new ShellSubscriber(uidMap, pullerManager, std::make_shared<LogEventFilter>());
 
     size_t bufferSize = config.ByteSize();
     vector<uint8_t> buffer(bufferSize);
@@ -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"([0-9]+$)");  // 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);
@@ -364,7 +373,7 @@
             .Times(1)
             .RetiresOnSaturation();
 
-    vector<bool> results(maxSubs, false);
+    vector<uint8_t> results(maxSubs, false);
 
     std::shared_ptr<MockStatsSubscriptionCallback> callbacks[maxSubs];
     for (int i = 0; i < maxSubs; i++) {
@@ -806,7 +815,7 @@
     sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     sp<ShellSubscriber> shellManager =
-            new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
+            new ShellSubscriber(uidMap, pullerManager, std::make_shared<LogEventFilter>());
 
     // set up 2 pipes for read/write config and data
     int fds_config[2];
@@ -830,7 +839,7 @@
     sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     sp<ShellSubscriber> shellManager =
-            new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
+            new ShellSubscriber(uidMap, pullerManager, std::make_shared<LogEventFilter>());
 
     // create a simple config to get screen events
     ShellSubscription config;
@@ -880,7 +889,7 @@
     sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     sp<ShellSubscriber> shellManager =
-            new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
+            new ShellSubscriber(uidMap, pullerManager, std::make_shared<LogEventFilter>());
 
     // number of different configs
     int numConfigs = 2;
@@ -957,6 +966,28 @@
     // Not closing fds_datas[i][0] because this causes writes within ShellSubscriberClient to hang
 }
 
+TEST(ShellSubscriberTest, testPushedSubscriptionRestrictedEvent) {
+    sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    std::vector<shared_ptr<LogEvent>> pushedList;
+    pushedList.push_back(CreateRestrictedLogEvent(/*atomTag=*/10, /*timestamp=*/1000));
+
+    // create a simple config to get screen events
+    ShellSubscription config;
+    config.add_pushed()->set_atom_id(10);
+
+    // expect empty data
+    vector<ShellData> expectedData;
+
+    // Test with single client
+    TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData,
+               kSingleClient);
+
+    // Test with multiple client
+    TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData, kNumClients);
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/state/StateTracker_test.cpp b/statsd/tests/state/StateTracker_test.cpp
index d2fbb3f..de53bd0 100644
--- a/statsd/tests/state/StateTracker_test.cpp
+++ b/statsd/tests/state/StateTracker_test.cpp
@@ -31,6 +31,7 @@
 namespace statsd {
 
 const int32_t timestampNs = 1000;
+const int32_t kStateUnknown = -1;
 
 /**
  * Mock StateListener class for testing.
@@ -451,28 +452,6 @@
               getStateInt(mgr, util::OVERLAY_STATE_CHANGED, queryKey));
 }
 
-/**
- * Test StateManager's onLogEvent and StateListener's onStateChanged
- * when there is an error extracting state from log event. Listener is not
- * updated of state change.
- */
-TEST(StateTrackerTest, TestStateChangeEventError) {
-    sp<TestStateListener> listener1 = new TestStateListener();
-    StateManager mgr;
-    mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener1);
-
-    // log event
-    std::shared_ptr<LogEvent> event1 =
-            buildIncorrectOverlayEvent(1000 /* uid */, "package1", 1 /* state */);
-    std::shared_ptr<LogEvent> event2 = buildOverlayEventBadStateType(1001 /* uid */, "package2");
-
-    // check listener was updated
-    mgr.onLogEvent(*event1);
-    ASSERT_EQ(0, listener1->updates.size());
-    mgr.onLogEvent(*event2);
-    ASSERT_EQ(0, listener1->updates.size());
-}
-
 TEST(StateTrackerTest, TestStateQuery) {
     sp<TestStateListener> listener1 = new TestStateListener();
     sp<TestStateListener> listener2 = new TestStateListener();
@@ -564,6 +543,54 @@
               getStateInt(mgr, util::WAKELOCK_STATE_CHANGED, queryKey5));
 }
 
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged
+ * when there is an error extracting state from log event. Listener is not
+ * updated of state change.
+ */
+TEST(StateTrackerTest, TestMalformedStateEvent_NoExistingStateValue) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(util::OVERLAY_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event1 =
+            buildIncorrectOverlayEvent(1000 /* uid */, "package1", 1 /* state */);
+    std::shared_ptr<LogEvent> event2 = buildOverlayEventBadStateType(1001 /* uid */, "package2");
+
+    // check listener was not updated
+    mgr.onLogEvent(*event1);
+    ASSERT_EQ(0, listener1->updates.size());
+    mgr.onLogEvent(*event2);
+    ASSERT_EQ(0, listener1->updates.size());
+}
+
+TEST(StateTrackerTest, TestMalformedStateEvent_ExistingStateValue) {
+    sp<TestStateListener> listener = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(util::PLUGGED_STATE_CHANGED, listener);
+
+    std::unique_ptr<LogEvent> event1 = CreateBatteryStateChangedEvent(
+            timestampNs, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+    mgr.onLogEvent(*event1);
+    ASSERT_EQ(1, listener->updates.size());
+    EXPECT_EQ(BatteryPluggedStateEnum::BATTERY_PLUGGED_USB, listener->updates[0].mState);
+    FieldValue stateFieldValue;
+    mgr.getStateValue(util::PLUGGED_STATE_CHANGED, listener->updates[0].mKey, &stateFieldValue);
+    EXPECT_EQ(BatteryPluggedStateEnum::BATTERY_PLUGGED_USB, stateFieldValue.mValue.int_value);
+    listener->updates.clear();
+
+    // Malformed event.
+    std::unique_ptr<LogEvent> event2 = CreateMalformedBatteryStateChangedEvent(timestampNs + 1000);
+    mgr.onLogEvent(*event2);
+    ASSERT_EQ(1, listener->updates.size());
+    EXPECT_EQ(kStateUnknown, listener->updates[0].mState);
+    EXPECT_FALSE(mgr.getStateValue(util::PLUGGED_STATE_CHANGED, listener->updates[0].mKey,
+                                   &stateFieldValue));
+    EXPECT_EQ(kStateUnknown, stateFieldValue.mValue.int_value);
+    listener->updates.clear();
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/statsd_test_util.cpp b/statsd/tests/statsd_test_util.cpp
index fc99528..539c890 100644
--- a/statsd/tests/statsd_test_util.cpp
+++ b/statsd/tests/statsd_test_util.cpp
@@ -20,7 +20,6 @@
 #include <android-base/stringprintf.h>
 
 #include "matchers/SimpleAtomMatchingTracker.h"
-#include "stats_annotations.h"
 #include "stats_event.h"
 #include "stats_util.h"
 
@@ -50,7 +49,7 @@
     ConfigMetricsReportList reports;
     reports.ParseFromArray(output.data(), output.size());
     EXPECT_EQ(1, reports.reports_size());
-    return reports.reports(kCallingUid);
+    return reports.reports(0);
 }
 
 StatsLogReport outputStreamToProto(ProtoOutputStream* proto) {
@@ -812,7 +811,7 @@
     AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
 
     AStatsEvent_writeInt32(statsEvent, uid);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     AStatsEvent_writeInt32(statsEvent, data1);
     AStatsEvent_writeInt32(statsEvent, data2);
 
@@ -825,7 +824,7 @@
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
     AStatsEvent_writeInt32(statsEvent, uid);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     AStatsEvent_writeInt32(statsEvent, data1);
     AStatsEvent_writeInt32Array(statsEvent, data2.data(), data2.size());
 
@@ -855,7 +854,7 @@
     AStatsEvent* statsEvent = makeUidStatsEvent(atomId, eventTimeNs, uid1, data1, data2);
     for (const int extraUid : extraUids) {
         AStatsEvent_writeInt32(statsEvent, extraUid);
-        AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+        AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     }
 
     shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
@@ -869,7 +868,7 @@
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
     AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size());
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
 
     shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
     parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -883,7 +882,7 @@
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
     AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size());
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     AStatsEvent_writeInt32(statsEvent, data1);
     AStatsEvent_writeInt32(statsEvent, data2);
 
@@ -900,7 +899,7 @@
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
     AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size());
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     AStatsEvent_writeInt32(statsEvent, data1);
     AStatsEvent_writeInt32Array(statsEvent, data2.data(), data2.size());
 
@@ -953,8 +952,8 @@
     AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, false);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(loggerUid, /*pid=*/0);
     parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -966,8 +965,8 @@
     AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
     AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, false);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
     parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -979,19 +978,36 @@
     AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
     AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, false);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
     parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
-std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) {
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs,
+                                                         const BatteryPluggedStateEnum state,
+                                                         int32_t uid) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
     AStatsEvent_writeInt32(statsEvent, state);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/uid, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateMalformedBatteryStateChangedEvent(const uint64_t timestampNs) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    AStatsEvent_writeString(statsEvent, "bad_state");
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
     parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -1063,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,
@@ -1112,14 +1140,15 @@
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
     writeAttribution(statsEvent, attributionUids, attributionTags);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID,
+                                  true);
     AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
     AStatsEvent_writeString(statsEvent, wakelockName.c_str());
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, true);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
     parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -1143,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);
@@ -1159,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);
 }
 
@@ -1258,11 +1288,11 @@
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
     AStatsEvent_writeInt32(statsEvent, uid);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, false);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
     parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -1280,20 +1310,21 @@
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
     writeAttribution(statsEvent, attributionUids, attributionTags);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID,
+                                  true);
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, true);
     if (state == util::BLE_SCAN_STATE_CHANGED__STATE__RESET) {
-        AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_TRIGGER_STATE_RESET,
+        AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET,
                                        util::BLE_SCAN_STATE_CHANGED__STATE__OFF);
     }
     AStatsEvent_writeBool(statsEvent, filtered);  // filtered
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
     AStatsEvent_writeBool(statsEvent, firstMatch);  // first match
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
     AStatsEvent_writeBool(statsEvent, opportunistic);  // opportunistic
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
     parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -1309,14 +1340,14 @@
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
     AStatsEvent_writeInt32(statsEvent, uid);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
     AStatsEvent_writeString(statsEvent, packageName.c_str());
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true);
     AStatsEvent_writeBool(statsEvent, usingAlertWindow);
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_EXCLUSIVE_STATE, true);
-    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_STATE_NESTED, false);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_STATE_NESTED, false);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
     parseStatsEventToLogEvent(statsEvent, logEvent.get());
@@ -1360,6 +1391,28 @@
     return logEvent;
 }
 
+std::unique_ptr<LogEvent> CreateRestrictedLogEvent(int atomTag, int64_t timestampNs) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomTag);
+    AStatsEvent_addInt32Annotation(statsEvent, ASTATSLOG_ANNOTATION_ID_RESTRICTION_CATEGORY,
+                                   ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC);
+    AStatsEvent_writeInt32(statsEvent, 10);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
+std::unique_ptr<LogEvent> CreateNonRestrictedLogEvent(int atomTag, int64_t timestampNs) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomTag);
+    AStatsEvent_writeInt32(statsEvent, 10);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
 std::unique_ptr<LogEvent> CreatePhoneSignalStrengthChangedEvent(
         int64_t timestampNs, ::telephony::SignalStrengthEnum state) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
@@ -1394,7 +1447,8 @@
     sp<StatsLogProcessor> processor = new StatsLogProcessor(
             uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs,
             [](const ConfigKey&) { return true; },
-            [](const int&, const vector<int64_t>&) { return true; }, logEventFilter);
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, logEventFilter);
 
     processor->OnConfigUpdated(currentTimeNs, key, config);
     return processor;
@@ -1450,8 +1504,8 @@
     }
     uint64_t matcherHash = 0x12345678;
     int64_t matcherId = 678;
-    return new EventMatcherWizard({new SimpleAtomMatchingTracker(
-            matcherId, matcherIndex, matcherHash, atomMatcher, uidMap)});
+    return new EventMatcherWizard(
+            {new SimpleAtomMatchingTracker(matcherId, matcherHash, atomMatcher, uidMap)});
 }
 
 StatsDimensionsValueParcel CreateAttributionUidDimensionsValueParcel(const int atomId,
@@ -2114,7 +2168,7 @@
 vector<PackageInfo> buildPackageInfos(
         const vector<string>& names, const vector<int32_t>& uids, const vector<int64_t>& versions,
         const vector<string>& versionStrings, const vector<string>& installers,
-        const vector<vector<uint8_t>>& certHashes, const vector<bool>& deleted,
+        const vector<vector<uint8_t>>& certHashes, const vector<uint8_t>& deleted,
         const vector<uint32_t>& installerIndices, const bool hashStrings) {
     vector<PackageInfo> packageInfos;
     for (int i = 0; i < uids.size(); i++) {
@@ -2207,6 +2261,66 @@
     return statsReport;
 }
 
+StatsdConfig buildGoodConfig(int configId) {
+    StatsdConfig config;
+    config.set_id(configId);
+
+    AtomMatcher screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
+    *config.add_atom_matcher() = screenOnMatcher;
+    *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+
+    AtomMatcher* eventMatcher = config.add_atom_matcher();
+    eventMatcher->set_id(StringToId("SCREEN_ON_OR_OFF"));
+    AtomMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher(screenOnMatcher.id());
+    combination->add_matcher(StringToId("ScreenTurnedOff"));
+
+    CountMetric* countMetric = config.add_count_metric();
+    *countMetric = createCountMetric("Count", screenOnMatcher.id() /* what */,
+                                     nullopt /* condition */, {} /* states */);
+    countMetric->mutable_dimensions_in_what()->set_field(SCREEN_STATE_ATOM_ID);
+    countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);
+
+    config.add_no_report_metric(StringToId("Count"));
+
+    *config.add_predicate() = CreateScreenIsOnPredicate();
+    *config.add_duration_metric() =
+            createDurationMetric("Duration", StringToId("ScreenIsOn") /* what */,
+                                 nullopt /* condition */, {} /* states */);
+
+    *config.add_gauge_metric() = createGaugeMetric(
+            "Gauge", screenOnMatcher.id() /* what */, GaugeMetric_SamplingType_FIRST_N_SAMPLES,
+            nullopt /* condition */, nullopt /* triggerEvent */);
+
+    *config.add_value_metric() =
+            createValueMetric("Value", screenOnMatcher /* what */, 2 /* valueField */,
+                              nullopt /* condition */, {} /* states */);
+
+    *config.add_kll_metric() = createKllMetric("Kll", screenOnMatcher /* what */, 2 /* kllField */,
+                                               nullopt /* condition */);
+
+    return config;
+}
+
+StatsdConfig buildGoodConfig(int configId, int alertId) {
+    StatsdConfig config = buildGoodConfig(configId);
+    auto alert = config.add_alert();
+    alert->set_id(alertId);
+    alert->set_metric_id(StringToId("Count"));
+    alert->set_num_buckets(10);
+    alert->set_refractory_period_secs(100);
+    alert->set_trigger_if_sum_gt(100);
+
+    return config;
+}
+
+sp<MockConfigMetadataProvider> makeMockConfigMetadataProvider(bool enabled) {
+    sp<MockConfigMetadataProvider> metadataProvider = new StrictMock<MockConfigMetadataProvider>();
+    EXPECT_CALL(*metadataProvider, useV2SoftMemoryCalculation()).Times(AnyNumber());
+    EXPECT_CALL(*metadataProvider, useV2SoftMemoryCalculation()).WillRepeatedly(Return(enabled));
+    return nullptr;
+}
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/statsd_test_util.h b/statsd/tests/statsd_test_util.h
index 4cd84e4..8fe6440 100644
--- a/statsd/tests/statsd_test_util.h
+++ b/statsd/tests/statsd_test_util.h
@@ -16,6 +16,7 @@
 
 #include <aidl/android/os/BnPendingIntentRef.h>
 #include <aidl/android/os/BnPullAtomCallback.h>
+#include <aidl/android/os/BnStatsQueryCallback.h>
 #include <aidl/android/os/BnStatsSubscriptionCallback.h>
 #include <aidl/android/os/IPullAtomCallback.h>
 #include <aidl/android/os/IPullAtomResultReceiver.h>
@@ -33,6 +34,7 @@
 #include "src/stats_log.pb.h"
 #include "src/stats_log_util.h"
 #include "src/statsd_config.pb.h"
+#include "stats_annotations.h"
 #include "stats_event.h"
 #include "statslog_statsdtest.h"
 
@@ -42,6 +44,7 @@
 
 using namespace testing;
 using ::aidl::android::os::BnPullAtomCallback;
+using ::aidl::android::os::BnStatsQueryCallback;
 using ::aidl::android::os::BnStatsSubscriptionCallback;
 using ::aidl::android::os::IPullAtomCallback;
 using ::aidl::android::os::IPullAtomResultReceiver;
@@ -93,6 +96,7 @@
 public:
     MOCK_METHOD1(sendDataBroadcast, Status(int64_t lastReportTimeNs));
     MOCK_METHOD1(sendActiveConfigsChangedBroadcast, Status(const vector<int64_t>& configIds));
+    MOCK_METHOD1(sendRestrictedMetricsChangedBroadcast, Status(const vector<int64_t>& metricIds));
     MOCK_METHOD6(sendSubscriberBroadcast,
                  Status(int64_t configUid, int64_t configId, int64_t subscriptionId,
                         int64_t subscriptionRuleId, const vector<string>& cookies,
@@ -101,6 +105,14 @@
 
 typedef StrictMock<BasicMockLogEventFilter> MockLogEventFilter;
 
+class MockStatsQueryCallback : public BnStatsQueryCallback {
+public:
+    MOCK_METHOD4(sendResults,
+                 Status(const vector<string>& queryData, const vector<string>& columnNames,
+                        const vector<int32_t>& columnTypes, int32_t rowCount));
+    MOCK_METHOD1(sendFailure, Status(const string& in_error));
+};
+
 class MockStatsSubscriptionCallback : public BnStatsSubscriptionCallback {
 public:
     MOCK_METHOD(Status, onSubscriptionData,
@@ -113,7 +125,8 @@
 protected:
     shared_ptr<StatsService> service;
     const int kConfigKey = 789130123;  // Randomly chosen
-    const int kCallingUid = 0;         // Randomly chosen
+    const int kCallingUid = 10100;     // Randomly chosen
+
     void SetUp() override {
         service = createStatsService();
         // Removing config file from data/misc/stats-service and data/misc/stats-data if present
@@ -135,7 +148,7 @@
 
     virtual shared_ptr<StatsService> createStatsService() {
         return SharedRefBase::make<StatsService>(new UidMap(), /* queue */ nullptr,
-                                                 /* LogEventFilter */ nullptr);
+                                                 std::make_shared<LogEventFilter>());
     }
 
     bool sendConfig(const StatsdConfig& config);
@@ -321,31 +334,29 @@
                                                     const std::vector<Position>& positions,
                                                     const std::vector<int>& fields);
 
-EventMetric createEventMetric(const string& name, const int64_t what,
-                              const optional<int64_t>& condition);
+EventMetric createEventMetric(const string& name, int64_t what, const optional<int64_t>& condition);
 
-CountMetric createCountMetric(const string& name, const int64_t what,
-                              const optional<int64_t>& condition, const vector<int64_t>& states);
+CountMetric createCountMetric(const string& name, int64_t what, const optional<int64_t>& condition,
+                              const vector<int64_t>& states);
 
-DurationMetric createDurationMetric(const string& name, const int64_t what,
+DurationMetric createDurationMetric(const string& name, int64_t what,
                                     const optional<int64_t>& condition,
                                     const vector<int64_t>& states);
 
-GaugeMetric createGaugeMetric(const string& name, const int64_t what,
+GaugeMetric createGaugeMetric(const string& name, int64_t what,
                               const GaugeMetric::SamplingType samplingType,
                               const optional<int64_t>& condition,
                               const optional<int64_t>& triggerEvent);
 
-ValueMetric createValueMetric(const string& name, const AtomMatcher& what, const int valueField,
+ValueMetric createValueMetric(const string& name, const AtomMatcher& what, int valueField,
                               const optional<int64_t>& condition, const vector<int64_t>& states);
 
-KllMetric createKllMetric(const string& name, const AtomMatcher& what, const int kllField,
+KllMetric createKllMetric(const string& name, const AtomMatcher& what, int kllField,
                           const optional<int64_t>& condition);
 
-Alert createAlert(const string& name, const int64_t metricId, const int buckets,
-                  const int64_t triggerSum);
+Alert createAlert(const string& name, int64_t metricId, int buckets, const int64_t triggerSum);
 
-Alarm createAlarm(const string& name, const int64_t offsetMillis, const int64_t periodMillis);
+Alarm createAlarm(const string& name, int64_t offsetMillis, int64_t periodMillis);
 
 Subscription createSubscription(const string& name, const Subscription_RuleType type,
                                 const int64_t ruleId);
@@ -457,13 +468,22 @@
 std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs);
 
 // Create log event when battery state changes.
-std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state);
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs,
+                                                         const BatteryPluggedStateEnum state,
+                                                         int32_t uid = AID_ROOT);
+
+// 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);
+std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, int uid);
 
 // Create log event for app moving to foreground.
-std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(uint64_t timestampNs, const int uid);
+std::unique_ptr<LogEvent> CreateMoveToForegroundEvent(uint64_t timestampNs, int uid);
 
 // Create log event when the app sync starts.
 std::unique_ptr<LogEvent> CreateSyncStartEvent(uint64_t timestampNs, const vector<int>& uids,
@@ -474,10 +494,10 @@
                                              const vector<string>& tags, const string& name);
 
 // Create log event when the app sync ends.
-std::unique_ptr<LogEvent> CreateAppCrashEvent(uint64_t timestampNs, const int uid);
+std::unique_ptr<LogEvent> CreateAppCrashEvent(uint64_t timestampNs, int uid);
 
 // Create log event for an app crash.
-std::unique_ptr<LogEvent> CreateAppCrashOccurredEvent(uint64_t timestampNs, const int uid);
+std::unique_ptr<LogEvent> CreateAppCrashOccurredEvent(uint64_t timestampNs, int uid);
 
 // Create log event for acquiring wakelock.
 std::unique_ptr<LogEvent> CreateAcquireWakelockEvent(uint64_t timestampNs, const vector<int>& uids,
@@ -510,7 +530,7 @@
                                                          const OverlayStateChanged::State state);
 
 std::unique_ptr<LogEvent> CreateAppStartOccurredEvent(
-        uint64_t timestampNs, const int uid, const string& pkg_name,
+        uint64_t timestampNs, int uid, const string& pkg_name,
         AppStartOccurred::TransitionType type, const string& activity_name,
         const string& calling_pkg_name, const bool is_instant_app, int64_t activity_start_msec);
 
@@ -525,12 +545,19 @@
         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);
+
 std::unique_ptr<LogEvent> CreatePhoneSignalStrengthChangedEvent(
         int64_t timestampNs, ::telephony::SignalStrengthEnum state);
 
 std::unique_ptr<LogEvent> CreateTestAtomReportedEvent(
         uint64_t timestampNs, const vector<int>& attributionUids,
-        const vector<string>& attributionTags, const int intField, const long longField,
+        const vector<string>& attributionTags, int intField, const long longField,
         const float floatField, const string& stringField, const bool boolField,
         const TestAtomReported::State enumField, const vector<uint8_t>& bytesField,
         const vector<int>& repeatedIntField, const vector<int64_t>& repeatedLongField,
@@ -544,10 +571,10 @@
 
 // Create a statsd log event processor upon the start time in seconds, config and key.
 sp<StatsLogProcessor> CreateStatsLogProcessor(
-        const int64_t timeBaseNs, const int64_t currentTimeNs, const StatsdConfig& config,
+        const int64_t timeBaseNs, int64_t currentTimeNs, const StatsdConfig& config,
         const ConfigKey& key, const shared_ptr<IPullAtomCallback>& puller = nullptr,
         const int32_t atomTag = 0 /*for puller only*/, const sp<UidMap> = new UidMap(),
-        const shared_ptr<LogEventFilter>& logEventFilter = nullptr);
+        const shared_ptr<LogEventFilter>& logEventFilter = std::make_shared<LogEventFilter>());
 
 LogEventFilter::AtomIdSet CreateAtomIdSetDefault();
 LogEventFilter::AtomIdSet CreateAtomIdSetFromConfig(const StatsdConfig& config);
@@ -564,7 +591,7 @@
                                                                      const int uid);
 
 void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid);
-void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, int atomId,
                                                    const int uid, const string& tag);
 void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid);
 void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid);
@@ -691,8 +718,8 @@
 }
 
 template <typename T>
-void backfillStartEndTimestampForFullBucket(
-    const int64_t timeBaseNs, const int64_t bucketSizeNs, T* bucket) {
+void backfillStartEndTimestampForFullBucket(const int64_t timeBaseNs, int64_t bucketSizeNs,
+                                            T* bucket) {
     bucket->set_start_bucket_elapsed_nanos(timeBaseNs + bucketSizeNs * bucket->bucket_num());
     bucket->set_end_bucket_elapsed_nanos(
         timeBaseNs + bucketSizeNs * bucket->bucket_num() + bucketSizeNs);
@@ -714,7 +741,7 @@
 }
 
 template <typename T>
-void backfillStartEndTimestampForMetrics(const int64_t timeBaseNs, const int64_t bucketSizeNs,
+void backfillStartEndTimestampForMetrics(const int64_t timeBaseNs, int64_t bucketSizeNs,
                                          T* metrics) {
     for (int i = 0; i < metrics->data_size(); ++i) {
         auto data = metrics->mutable_data(i);
@@ -769,10 +796,10 @@
 
 PackageInfoSnapshot getPackageInfoSnapshot(const sp<UidMap> uidMap);
 
-ApplicationInfo createApplicationInfo(const int32_t uid, const int64_t version,
-                                      const string& versionString, const string& package);
+ApplicationInfo createApplicationInfo(int32_t uid, int64_t version, const string& versionString,
+                                      const string& package);
 
-PackageInfo buildPackageInfo(const std::string& name, const int32_t uid, const int64_t version,
+PackageInfo buildPackageInfo(const std::string& name, const int32_t uid, int64_t version,
                              const std::string& versionString,
                              const std::optional<std::string> installer,
                              const std::vector<uint8_t>& certHash, const bool deleted,
@@ -782,7 +809,7 @@
         const std::vector<string>& names, const std::vector<int32_t>& uids,
         const std::vector<int64_t>& versions, const std::vector<std::string>& versionStrings,
         const std::vector<std::string>& installers,
-        const std::vector<std::vector<uint8_t>>& certHashes, const std::vector<bool>& deleted,
+        const std::vector<std::vector<uint8_t>>& certHashes, const std::vector<uint8_t>& deleted,
         const std::vector<uint32_t>& installerIndices, const bool hashStrings);
 
 template <typename T>
@@ -805,6 +832,18 @@
     proto.SerializeToArray(bytes.data(), byteSize);
     return bytes;
 }
+
+StatsdConfig buildGoodConfig(int configId);
+
+StatsdConfig buildGoodConfig(int configId, int alertId);
+
+class MockConfigMetadataProvider : public ConfigMetadataProvider {
+public:
+    MOCK_METHOD(bool, useV2SoftMemoryCalculation, (), (override));
+};
+
+sp<MockConfigMetadataProvider> makeMockConfigMetadataProvider(bool enabled);
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/storage/StorageManager_test.cpp b/statsd/tests/storage/StorageManager_test.cpp
index 96409e6..27b8e98 100644
--- a/statsd/tests/storage/StorageManager_test.cpp
+++ b/statsd/tests/storage/StorageManager_test.cpp
@@ -21,6 +21,8 @@
 
 #include "android-base/stringprintf.h"
 #include "stats_log_util.h"
+#include "tests/statsd_test_util.h"
+#include "utils/DbUtils.h"
 
 #ifdef __ANDROID__
 
@@ -29,6 +31,7 @@
 namespace statsd {
 
 using namespace testing;
+
 using std::make_shared;
 using std::shared_ptr;
 using std::vector;
@@ -278,6 +281,42 @@
     EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds);
 }
 
+TEST(StorageManagerTest, DeleteUnmodifiedOldDbFiles) {
+    if (!isAtLeastU()) {
+        GTEST_SKIP();
+    }
+    ConfigKey key(123, 12345);
+    unique_ptr<LogEvent> event = CreateRestrictedLogEvent(/*atomTag=*/10, /*timestampNs=*/1000);
+    dbutils::createTableIfNeeded(key, /*metricId=*/1, *event);
+    EXPECT_TRUE(StorageManager::hasFile(
+            base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+
+    int64_t wallClockSec = getWallClockSec() + (StatsdStats::kMaxAgeSecond + 1);
+    StorageManager::enforceDbGuardrails(STATS_RESTRICTED_DATA_DIR, wallClockSec,
+                                        /*maxBytes=*/INT_MAX);
+
+    EXPECT_FALSE(StorageManager::hasFile(
+            base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+}
+
+TEST(StorageManagerTest, DeleteLargeDbFiles) {
+    if (!isAtLeastU()) {
+        GTEST_SKIP();
+    }
+    ConfigKey key(123, 12345);
+    unique_ptr<LogEvent> event = CreateRestrictedLogEvent(/*atomTag=*/10, /*timestampNs=*/1000);
+    dbutils::createTableIfNeeded(key, /*metricId=*/1, *event);
+    EXPECT_TRUE(StorageManager::hasFile(
+            base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+
+    StorageManager::enforceDbGuardrails(STATS_RESTRICTED_DATA_DIR,
+                                        /*wallClockSec=*/getWallClockSec(),
+                                        /*maxBytes=*/0);
+
+    EXPECT_FALSE(StorageManager::hasFile(
+            base::StringPrintf("%s/%s", STATS_RESTRICTED_DATA_DIR, "123_12345.db").c_str()));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/utils/DbUtils_test.cpp b/statsd/tests/utils/DbUtils_test.cpp
new file mode 100644
index 0000000..9bc1d61
--- /dev/null
+++ b/statsd/tests/utils/DbUtils_test.cpp
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/DbUtils.h"
+
+#include <gtest/gtest.h>
+
+#include "android-base/stringprintf.h"
+#include "storage/StorageManager.h"
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+namespace dbutils {
+
+using base::StringPrintf;
+
+namespace {
+const ConfigKey key = ConfigKey(111, 222);
+const int64_t metricId = 111;
+const int32_t tagId = 1;
+
+AStatsEvent* makeAStatsEvent(int32_t atomId, int64_t timestampNs) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+    return statsEvent;
+}
+
+LogEvent makeLogEvent(AStatsEvent* statsEvent) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, &event);
+    return event;
+}
+
+class DbUtilsTest : public ::testing::Test {
+public:
+    void SetUp() override {
+        if (!isAtLeastU()) {
+            GTEST_SKIP();
+        }
+    }
+    void TearDown() override {
+        if (!isAtLeastU()) {
+            GTEST_SKIP();
+        }
+        deleteDb(key);
+    }
+};
+}  // Anonymous namespace.
+
+TEST_F(DbUtilsTest, TestInsertString) {
+    int64_t eventElapsedTimeNs = 10000000000;
+
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+    AStatsEvent_writeString(statsEvent, "test_string");
+    LogEvent logEvent = makeLogEvent(statsEvent);
+    vector<LogEvent> events{logEvent};
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+    string err;
+    EXPECT_TRUE(insert(key, metricId, events, err));
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+    EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, "test_string"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestMaliciousString) {
+    int64_t eventElapsedTimeNs = 10000000000;
+
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+    AStatsEvent_writeString(statsEvent, "111); DROP TABLE metric_111;--");
+    LogEvent logEvent = makeLogEvent(statsEvent);
+    vector<LogEvent> events{logEvent};
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+    string err;
+    EXPECT_TRUE(insert(key, metricId, events, err));
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+    EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _,
+                                     "111); DROP TABLE metric_111;--"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestInsertStringNegativeMetricId) {
+    int64_t eventElapsedTimeNs = 10000000000;
+    int64_t metricId2 = -111;
+
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+    AStatsEvent_writeString(statsEvent, "111");
+    LogEvent logEvent = makeLogEvent(statsEvent);
+    vector<LogEvent> events{logEvent};
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId2, logEvent));
+    string err;
+    EXPECT_TRUE(insert(key, metricId2, events, err));
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM metric_n111 ORDER BY elapsedTimestampNs";
+    EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, "111"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestInsertInteger) {
+    int64_t eventElapsedTimeNs = 10000000000;
+
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+    AStatsEvent_writeInt32(statsEvent, 11);
+    AStatsEvent_writeInt64(statsEvent, 111);
+    LogEvent logEvent = makeLogEvent(statsEvent);
+    vector<LogEvent> events{logEvent};
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+    string err;
+    EXPECT_TRUE(insert(key, metricId, events, err));
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+    EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, "11", "111"));
+    EXPECT_THAT(columnTypes, ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER,
+                                         SQLITE_INTEGER, SQLITE_INTEGER));
+    EXPECT_THAT(columnNames, ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs",
+                                         "field_1", "field_2"));
+}
+
+TEST_F(DbUtilsTest, TestInsertFloat) {
+    int64_t eventElapsedTimeNs = 10000000000;
+
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+    AStatsEvent_writeFloat(statsEvent, 11.0);
+    LogEvent logEvent = makeLogEvent(statsEvent);
+    vector<LogEvent> events{logEvent};
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+    string err;
+    EXPECT_TRUE(insert(key, metricId, events, err));
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+    EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, _));
+    EXPECT_FLOAT_EQ(/*field1=*/std::stof(rows[0][3]), 11.0);
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_FLOAT));
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestInsertTwoEvents) {
+    int64_t eventElapsedTimeNs = 10000000000;
+
+    AStatsEvent* statsEvent1 = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+    AStatsEvent_writeString(statsEvent1, "111");
+    LogEvent logEvent1 = makeLogEvent(statsEvent1);
+
+    AStatsEvent* statsEvent2 = makeAStatsEvent(tagId, eventElapsedTimeNs + 20);
+    AStatsEvent_writeString(statsEvent2, "222");
+    LogEvent logEvent2 = makeLogEvent(statsEvent2);
+
+    vector<LogEvent> events{logEvent1, logEvent2};
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent1));
+    string err;
+    EXPECT_TRUE(insert(key, metricId, events, err));
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+    EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+    ASSERT_EQ(rows.size(), 2);
+    EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, "111"));
+    EXPECT_THAT(rows[1], ElementsAre("1", to_string(eventElapsedTimeNs + 20), _, "222"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestInsertTwoEventsEnforceTtl) {
+    int64_t eventElapsedTimeNs = 10000000000;
+    int64_t eventWallClockNs = 50000000000;
+
+    AStatsEvent* statsEvent1 = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+    AStatsEvent_writeString(statsEvent1, "111");
+    LogEvent logEvent1 = makeLogEvent(statsEvent1);
+    logEvent1.setLogdWallClockTimestampNs(eventWallClockNs);
+
+    AStatsEvent* statsEvent2 = makeAStatsEvent(tagId, eventElapsedTimeNs + 20);
+    AStatsEvent_writeString(statsEvent2, "222");
+    LogEvent logEvent2 = makeLogEvent(statsEvent2);
+    logEvent2.setLogdWallClockTimestampNs(eventWallClockNs + eventElapsedTimeNs);
+
+    vector<LogEvent> events{logEvent1, logEvent2};
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent1));
+    sqlite3* db = getDb(key);
+    string err;
+    EXPECT_TRUE(insert(db, metricId, events, err));
+    EXPECT_TRUE(flushTtl(db, metricId, eventWallClockNs));
+    closeDb(db);
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+    EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 20), _, "222"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestMaliciousQuery) {
+    int64_t eventElapsedTimeNs = 10000000000;
+
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+    AStatsEvent_writeString(statsEvent, "111");
+    LogEvent logEvent = makeLogEvent(statsEvent);
+    vector<LogEvent> events{logEvent};
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+    string err;
+    EXPECT_TRUE(insert(key, metricId, events, err));
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "DROP TABLE metric_111";
+    EXPECT_FALSE(query(key, zSql, rows, columnTypes, columnNames, err));
+    EXPECT_THAT(err, StartsWith("attempt to write a readonly database"));
+}
+
+TEST_F(DbUtilsTest, TestInsertStringIntegrityCheckPasses) {
+    int64_t eventElapsedTimeNs = 10000000000;
+
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+    AStatsEvent_writeString(statsEvent, "111");
+    LogEvent logEvent = makeLogEvent(statsEvent);
+    vector<LogEvent> events{logEvent};
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+    string err;
+    EXPECT_TRUE(insert(key, metricId, events, err));
+    verifyIntegrityAndDeleteIfNecessary(key);
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+    EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(rows[0], ElementsAre("1", to_string(eventElapsedTimeNs + 10), _, "111"));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_INTEGER, SQLITE_TEXT));
+    EXPECT_THAT(columnNames,
+                ElementsAre("atomId", "elapsedTimestampNs", "wallTimestampNs", "field_1"));
+}
+
+TEST_F(DbUtilsTest, TestInsertStringIntegrityCheckFails) {
+    int64_t eventElapsedTimeNs = 10000000000;
+
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, eventElapsedTimeNs + 10);
+    AStatsEvent_writeString(statsEvent, "111");
+    LogEvent logEvent = makeLogEvent(statsEvent);
+    vector<LogEvent> events{logEvent};
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+    string err;
+    EXPECT_TRUE(insert(key, metricId, events, err));
+
+    vector<string> randomData{"1232hasha14125ashfas21512sh31321"};
+    string fileName = StringPrintf("%s/%d_%lld.db", STATS_RESTRICTED_DATA_DIR, key.GetUid(),
+                                   (long long)key.GetId());
+    StorageManager::writeFile(fileName.c_str(), randomData.data(), randomData.size());
+    EXPECT_TRUE(StorageManager::hasFile(fileName.c_str()));
+    verifyIntegrityAndDeleteIfNecessary(key);
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM metric_111 ORDER BY elapsedTimestampNs";
+    EXPECT_FALSE(query(key, zSql, rows, columnTypes, columnNames, err));
+    EXPECT_THAT(err, StartsWith("unable to open database file"));
+    EXPECT_FALSE(StorageManager::hasFile(fileName.c_str()));
+}
+
+TEST_F(DbUtilsTest, TestEventCompatibilityEventMatchesTable) {
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, /*eventElapsedTime=*/10000000000);
+    AStatsEvent_writeString(statsEvent, "111");
+    AStatsEvent_writeFloat(statsEvent, 111.0);
+    AStatsEvent_writeInt32(statsEvent, 23);
+    LogEvent logEvent = makeLogEvent(statsEvent);
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+
+    EXPECT_TRUE(isEventCompatible(key, metricId, logEvent));
+}
+
+TEST_F(DbUtilsTest, TestEventCompatibilityEventDoesNotMatchesTable) {
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, /*eventElapsedTime=*/10000000000);
+    AStatsEvent_writeString(statsEvent, "111");
+    AStatsEvent_writeFloat(statsEvent, 111.0);
+    AStatsEvent_writeInt32(statsEvent, 23);
+    LogEvent logEvent = makeLogEvent(statsEvent);
+
+    AStatsEvent* statsEvent2 = makeAStatsEvent(tagId, /*eventElapsedTime=*/10000000000);
+    AStatsEvent_writeString(statsEvent2, "111");
+    AStatsEvent_writeFloat(statsEvent2, 111.0);
+    AStatsEvent_writeInt32(statsEvent2, 23);
+    AStatsEvent_writeInt32(statsEvent2, 25);
+    LogEvent logEvent2 = makeLogEvent(statsEvent2);
+
+    EXPECT_TRUE(createTableIfNeeded(key, metricId, logEvent));
+
+    EXPECT_FALSE(isEventCompatible(key, metricId, logEvent2));
+}
+
+TEST_F(DbUtilsTest, TestEventCompatibilityTableNotCreated) {
+    AStatsEvent* statsEvent = makeAStatsEvent(tagId, /*eventElapsedTime=*/10000000000);
+    AStatsEvent_writeString(statsEvent, "111");
+    AStatsEvent_writeFloat(statsEvent, 111.0);
+    AStatsEvent_writeInt32(statsEvent, 23);
+    LogEvent logEvent = makeLogEvent(statsEvent);
+
+    EXPECT_TRUE(isEventCompatible(key, metricId, logEvent));
+}
+
+TEST_F(DbUtilsTest, TestUpdateDeviceInfoTable) {
+    string err;
+    updateDeviceInfoTable(key, err);
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM device_info";
+    EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(rows[0], ElementsAre(_, _, _, _, _, _, _, _, _, _));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT,
+                            SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT));
+    EXPECT_THAT(columnNames,
+                ElementsAre("sdkVersion", "model", "product", "hardware", "device", "osBuild",
+                            "fingerprint", "brand", "manufacturer", "board"));
+}
+
+TEST_F(DbUtilsTest, TestUpdateDeviceInfoTableInvokeTwice) {
+    string err;
+    updateDeviceInfoTable(key, err);
+    updateDeviceInfoTable(key, err);
+
+    std::vector<int32_t> columnTypes;
+    std::vector<string> columnNames;
+    std::vector<std::vector<std::string>> rows;
+    string zSql = "SELECT * FROM device_info";
+    EXPECT_TRUE(query(key, zSql, rows, columnTypes, columnNames, err));
+
+    ASSERT_EQ(rows.size(), 1);
+    EXPECT_THAT(rows[0], ElementsAre(_, _, _, _, _, _, _, _, _, _));
+    EXPECT_THAT(columnTypes,
+                ElementsAre(SQLITE_INTEGER, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT,
+                            SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT, SQLITE_TEXT));
+    EXPECT_THAT(columnNames,
+                ElementsAre("sdkVersion", "model", "product", "hardware", "device", "osBuild",
+                            "fingerprint", "brand", "manufacturer", "board"));
+}
+
+}  // namespace dbutils
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/statsd/tools/localtools/Android.bp b/statsd/tools/localtools/Android.bp
index c07cb4e..b4d3f91 100644
--- a/statsd/tools/localtools/Android.bp
+++ b/statsd/tools/localtools/Android.bp
@@ -11,6 +11,7 @@
     ],
     static_libs: [
         "platformprotos",
+        "statsd_extension_atoms_registry_lib",
         "guava",
     ],
 }
@@ -23,10 +24,21 @@
     ],
     static_libs: [
         "platformprotos",
+        "statsd_extension_atoms_registry_lib",
         "guava",
     ],
 }
 
+java_library_host {
+    name: "statsd_extension_atoms_registry_lib",
+    srcs: [
+        "src/com/android/statsd/shelltools/ExtensionAtomsRegistry.java",
+    ],
+    static_libs: [
+        "platformprotos",
+    ],
+}
+
 
 java_binary_host {
     name: "statsd_testdrive",
diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/ExtensionAtomsRegistry.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/ExtensionAtomsRegistry.java
new file mode 100644
index 0000000..489288e
--- /dev/null
+++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/ExtensionAtomsRegistry.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.statsd.shelltools;
+
+import com.android.internal.os.ExperimentIdsProto;
+import com.android.internal.os.UidDataProto;
+import com.android.os.ActiveConfigProto;
+import com.android.os.ShellConfig;
+import com.android.os.adservices.AdservicesExtensionAtoms;
+import com.android.os.art.ArtExtensionAtoms;
+import com.android.os.automotive.caruilib.AutomotiveCaruilibAtoms;
+import com.android.os.bluetooth.BluetoothExtensionAtoms;
+import com.android.os.devicelogs.DeviceLogsAtoms;
+import com.android.os.dnd.DndAtoms;
+import com.android.os.dnd.DndExtensionAtoms;
+import com.android.os.expresslog.ExpresslogExtensionAtoms;
+import com.android.os.framework.FrameworkExtensionAtoms;
+import com.android.os.gps.GpsAtoms;
+import com.android.os.grammaticalinflection.GrammaticalInflection;
+import com.android.os.hardware.biometrics.BiometricsAtoms;
+import com.android.os.healthfitness.api.ApiExtensionAtoms;
+import com.android.os.healthfitness.ui.UiExtensionAtoms;
+import com.android.os.hotword.HotwordAtoms;
+import com.android.os.kernel.KernelAtoms;
+import com.android.os.locale.LocaleAtoms;
+import com.android.os.location.LocationAtoms;
+import com.android.os.location.LocationExtensionAtoms;
+import com.android.os.media.MediaDrmAtoms;
+import com.android.os.memorysafety.MemorysafetyExtensionAtoms;
+import com.android.os.permissioncontroller.PermissioncontrollerExtensionAtoms;
+import com.android.os.providers.mediaprovider.MediaProviderAtoms;
+import com.android.os.settings.SettingsExtensionAtoms;
+import com.android.os.statsd.ShellDataProto;
+import com.android.os.sysui.SysuiAtoms;
+import com.android.os.telecom.TelecomExtensionAtom;
+import com.android.os.telephony.SatelliteExtensionAtoms;
+import com.android.os.telephony.TelephonyExtensionAtoms;
+import com.android.os.telephony.qns.QnsExtensionAtoms;
+import com.android.os.usb.UsbAtoms;
+import com.android.os.uwb.UwbExtensionAtoms;
+import com.android.os.view.inputmethod.InputmethodAtoms;
+import com.android.os.wear.media.WearMediaAtoms;
+import com.android.os.wear.media.WearMediaExtensionAtoms;
+import com.android.os.wearpas.WearpasExtensionAtoms;
+import com.android.os.wearservices.WearservicesAtoms;
+import com.android.os.wearservices.WearservicesExtensionAtoms;
+import com.android.os.wearsysui.WearsysuiAtoms;
+import com.android.os.wifi.WifiExtensionAtoms;
+import android.os.statsd.media.MediaCodecExtensionAtoms;
+import com.android.os.credentials.CredentialsExtensionAtoms;
+import com.android.os.sdksandbox.SdksandboxExtensionAtoms;
+import com.android.os.apex.ApexExtensionAtoms;
+
+import com.google.protobuf.ExtensionRegistry;
+
+/**
+ * CustomExtensionRegistry for local use of statsd.
+ */
+public class ExtensionAtomsRegistry {
+
+    public static ExtensionRegistry REGISTRY;
+
+    static {
+        /** In Java, when parsing a message containing extensions, you must provide an
+         * ExtensionRegistry which contains definitions of all of the extensions which you
+         * want the parser to recognize. This is necessary because Java's bytecode loading
+         * semantics do not provide any way for the protocol buffers library to automatically
+         * discover all extensions defined in your binary.
+         *
+         * See http://sites/protocol-buffers/user-docs/miscellaneous-howtos/extensions
+         * #Java_ExtensionRegistry_
+         */
+        REGISTRY = ExtensionRegistry.newInstance();
+        registerAllExtensions(REGISTRY);
+        REGISTRY = REGISTRY.getUnmodifiable();
+    }
+
+    /**
+     * Registers all proto2 extensions.
+     */
+    private static void registerAllExtensions(ExtensionRegistry extensionRegistry) {
+        ExperimentIdsProto.registerAllExtensions(extensionRegistry);
+        UidDataProto.registerAllExtensions(extensionRegistry);
+        ActiveConfigProto.registerAllExtensions(extensionRegistry);
+        ShellConfig.registerAllExtensions(extensionRegistry);
+        AdservicesExtensionAtoms.registerAllExtensions(extensionRegistry);
+        AutomotiveCaruilibAtoms.registerAllExtensions(extensionRegistry);
+        BluetoothExtensionAtoms.registerAllExtensions(extensionRegistry);
+        DeviceLogsAtoms.registerAllExtensions(extensionRegistry);
+        DndAtoms.registerAllExtensions(extensionRegistry);
+        DndExtensionAtoms.registerAllExtensions(extensionRegistry);
+        ExpresslogExtensionAtoms.registerAllExtensions(extensionRegistry);
+        FrameworkExtensionAtoms.registerAllExtensions(extensionRegistry);
+        GpsAtoms.registerAllExtensions(extensionRegistry);
+        GrammaticalInflection.registerAllExtensions(extensionRegistry);
+        BiometricsAtoms.registerAllExtensions(extensionRegistry);
+        ApiExtensionAtoms.registerAllExtensions(extensionRegistry);
+        UiExtensionAtoms.registerAllExtensions(extensionRegistry);
+        HotwordAtoms.registerAllExtensions(extensionRegistry);
+        KernelAtoms.registerAllExtensions(extensionRegistry);
+        LocaleAtoms.registerAllExtensions(extensionRegistry);
+        LocationAtoms.registerAllExtensions(extensionRegistry);
+        LocationExtensionAtoms.registerAllExtensions(extensionRegistry);
+        MediaDrmAtoms.registerAllExtensions(extensionRegistry);
+        MemorysafetyExtensionAtoms.registerAllExtensions(extensionRegistry);
+        PermissioncontrollerExtensionAtoms.registerAllExtensions(extensionRegistry);
+        MediaProviderAtoms.registerAllExtensions(extensionRegistry);
+        SettingsExtensionAtoms.registerAllExtensions(extensionRegistry);
+        ShellDataProto.registerAllExtensions(extensionRegistry);
+        SysuiAtoms.registerAllExtensions(extensionRegistry);
+        TelecomExtensionAtom.registerAllExtensions(extensionRegistry);
+        SatelliteExtensionAtoms.registerAllExtensions(extensionRegistry);
+        TelephonyExtensionAtoms.registerAllExtensions(extensionRegistry);
+        QnsExtensionAtoms.registerAllExtensions(extensionRegistry);
+        UsbAtoms.registerAllExtensions(extensionRegistry);
+        UwbExtensionAtoms.registerAllExtensions(extensionRegistry);
+        InputmethodAtoms.registerAllExtensions(extensionRegistry);
+        WearMediaAtoms.registerAllExtensions(extensionRegistry);
+        WearMediaExtensionAtoms.registerAllExtensions(extensionRegistry);
+        WearpasExtensionAtoms.registerAllExtensions(extensionRegistry);
+        WearservicesAtoms.registerAllExtensions(extensionRegistry);
+        WearservicesExtensionAtoms.registerAllExtensions(extensionRegistry);
+        WearsysuiAtoms.registerAllExtensions(extensionRegistry);
+        WifiExtensionAtoms.registerAllExtensions(extensionRegistry);
+        MediaCodecExtensionAtoms.registerAllExtensions(extensionRegistry);
+        CredentialsExtensionAtoms.registerAllExtensions(extensionRegistry);
+        SdksandboxExtensionAtoms.registerAllExtensions(extensionRegistry);
+        ArtExtensionAtoms.registerAllExtensions(extensionRegistry);
+        ApexExtensionAtoms.registerAllExtensions(extensionRegistry);
+    }
+}
diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
index 65fdb07..93293a8 100644
--- a/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
+++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
@@ -40,6 +40,8 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import com.android.statsd.shelltools.ExtensionAtomsRegistry;
+
 /**
  * Utilities for local use of statsd.
  */
@@ -112,7 +114,8 @@
                     "--include_current_bucket",
                     "--proto");
             ConfigMetricsReportList reportList =
-                    ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile));
+                    ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile),
+                            ExtensionAtomsRegistry.REGISTRY);
             return reportList;
         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
             logger.severe("Failed to fetch and parse the statsd output report. "
diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index 2b7602c..05b7fb5 100644
--- a/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -30,6 +30,7 @@
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.StatsLogReport;
+import com.android.os.telephony.qns.QnsExtensionAtoms;
 import com.android.statsd.shelltools.Utils;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -98,6 +99,9 @@
             "com.google.android.healthconnect.controller",
             "com.android.telephony.qns",
             "com.android.car",
+            "com.android.ondevicepersonalization.services",
+            "com.google.android.ondevicepersonalization.services",
+            "AID_UPROBESTATS",
     };
     private static final String[] DEFAULT_PULL_SOURCES = {
             "AID_KEYSTORE", "AID_RADIO", "AID_SYSTEM",
@@ -154,7 +158,8 @@
         LOGGER.severe("-e");
         LOGGER.severe("\tWait for Enter key press before collecting report");
         LOGGER.severe("-d delay_ms");
-        LOGGER.severe("\tWait for delay_ms before collecting report, default is 60000 ms");
+        LOGGER.severe("\tWait for delay_ms before collecting report, default is 60000 ms. Only");
+        LOGGER.severe("\taffects collection of pushed atoms.");
         LOGGER.severe("-v");
         LOGGER.severe("\tDebug logging level");
     }
@@ -597,6 +602,21 @@
                             PullAtomPackages.newBuilder()
                                     .setAtomId(Atom.LAUNCHER_LAYOUT_SNAPSHOT_FIELD_NUMBER)
                                     .addPackages("com.google.android.apps.nexuslauncher"))
+                    .addPullAtomPackages(
+                            PullAtomPackages.newBuilder()
+                                    .setAtomId(QnsExtensionAtoms
+                                            .QNS_RAT_PREFERENCE_MISMATCH_INFO_FIELD_NUMBER)
+                                    .addPackages("com.android.telephony.qns"))
+                    .addPullAtomPackages(
+                            PullAtomPackages.newBuilder()
+                                    .setAtomId(QnsExtensionAtoms
+                                            .QNS_HANDOVER_TIME_MILLIS_FIELD_NUMBER)
+                                    .addPackages("com.android.telephony.qns"))
+                    .addPullAtomPackages(
+                            PullAtomPackages.newBuilder()
+                                    .setAtomId(QnsExtensionAtoms
+                                            .QNS_HANDOVER_PINGPONG_FIELD_NUMBER)
+                                    .addPackages("com.android.telephony.qns"))
                     .setHashStringsInMetricReport(false);
         }
     }
diff --git a/tests/Android.bp b/tests/Android.bp
index 532ce63..71784a9 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -37,14 +37,17 @@
         "host-libprotobuf-java-full",
         "platformprotos",
         "tradefed",
-        "truth-prebuilt",
+        "truth",
     ],
     static_libs: [
         "core_cts_test_resources",
         "perfetto_config-full",
+        "cts-statsd-atom-host-test-utils",
     ],
     data: [
         "**/*.pbtxt",
         ":CtsStatsdApp",
+        ":StatsdAtomStormApp",
+        ":StatsdAtomStormApp2",
     ],
 }
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index e33c2a0..781813a 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -36,4 +36,10 @@
         <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
         <option name="restore-settings" value="true" />
     </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="StatsdAtomStormApp.apk" />
+        <option name="test-file-name" value="StatsdAtomStormApp2.apk" />
+    </target_preparer>
 </configuration>
diff --git a/tests/OWNERS b/tests/OWNERS
index e69ed54..6e97551 100644
--- a/tests/OWNERS
+++ b/tests/OWNERS
@@ -1,8 +1,2 @@
 # Bug component: 366902
-jeffreyhuang@google.com
-muhammadq@google.com
-sharaienko@google.com
-singhtejinder@google.com
-tsaichristine@google.com
-yaochen@google.com
-yro@google.com
+file:platform/packages/modules/StatsD:/OWNERS
diff --git a/tests/apps/atomstormapp/Android.bp b/tests/apps/atomstormapp/Android.bp
new file mode 100644
index 0000000..86e5953
--- /dev/null
+++ b/tests/apps/atomstormapp/Android.bp
@@ -0,0 +1,64 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "StatsdAtomStormApp",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    min_sdk_version: "24",
+    srcs: [
+        "src/**/*.java",
+    ],
+    libs: [
+        "android.test.runner",
+        "junit",
+        "org.apache.http.legacy",
+    ],
+    privileged: true,
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+        "androidx.legacy_legacy-support-v4",
+        "androidx.test.rules",
+    ],
+    compile_multilib: "both",
+}
+
+android_test_helper_app {
+    name: "StatsdAtomStormApp2",
+    manifest: "AndroidManifest2.xml",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    min_sdk_version: "24",
+    srcs: [
+        "src/**/*.java",
+    ],
+    libs: [
+        "android.test.runner",
+        "junit",
+        "org.apache.http.legacy",
+    ],
+    privileged: true,
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+        "androidx.legacy_legacy-support-v4",
+        "androidx.test.rules",
+    ],
+    compile_multilib: "both",
+}
diff --git a/tests/apps/atomstormapp/AndroidManifest.xml b/tests/apps/atomstormapp/AndroidManifest.xml
new file mode 100644
index 0000000..2993928
--- /dev/null
+++ b/tests/apps/atomstormapp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.statsd.app.atomstorm">
+    <!-- Using gms shared uid is necessary for the receivers to work. -->
+
+    <!-- GTS started at 14; do not change minSdkVersion. -->
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28"/>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.statsd.app.atomstorm"
+                     android:label="CTS tests of android.os.statsd stats collection">
+    </instrumentation>
+</manifest>
diff --git a/tests/apps/atomstormapp/AndroidManifest2.xml b/tests/apps/atomstormapp/AndroidManifest2.xml
new file mode 100644
index 0000000..310397f
--- /dev/null
+++ b/tests/apps/atomstormapp/AndroidManifest2.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.statsd.app.atomstorm.copy">
+    <!-- Using gms shared uid is necessary for the receivers to work. -->
+
+    <!-- GTS started at 14; do not change minSdkVersion. -->
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28"/>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.statsd.app.atomstorm.copy"
+                     android:label="CTS tests of android.os.statsd stats collection">
+    </instrumentation>
+</manifest>
diff --git a/tests/apps/atomstormapp/src/com/android/statsd/app/atomstorm/StatsdAtomStorm.java b/tests/apps/atomstormapp/src/com/android/statsd/app/atomstorm/StatsdAtomStorm.java
new file mode 100644
index 0000000..ce4ced9
--- /dev/null
+++ b/tests/apps/atomstormapp/src/com/android/statsd/app/atomstorm/StatsdAtomStorm.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statsd.app.atomstorm;
+
+import android.util.StatsLog;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class StatsdAtomStorm {
+    private static final int EventStormAtomsCount = 100000;
+
+    /** Tests socket overflow. */
+    @Test
+    public void testLogManyAtomsBackToBack() throws Exception {
+        // logging back to back many atoms to force socket overflow
+        performAtomStorm(EventStormAtomsCount);
+        // make pause to resolve socket overflow
+        Thread.sleep(100);
+        // give chance for libstatssocket send loss stats to statsd triggering successful logging
+        performAtomStorm(1);
+    }
+
+    private void performAtomStorm(int iterations) {
+        // single atom logging takes ~2us excluding JNI interactions
+        for (int i = 0; i < iterations; i++) {
+            StatsLog.logStart(i);
+            StatsLog.logStop(i);
+        }
+    }
+}
diff --git a/tests/apps/statsdapp/Android.bp b/tests/apps/statsdapp/Android.bp
index a45b95c..7dd3d19 100644
--- a/tests/apps/statsdapp/Android.bp
+++ b/tests/apps/statsdapp/Android.bp
@@ -51,7 +51,7 @@
         "androidx.legacy_legacy-support-v4",
         "androidx.test.rules",
         "cts-net-utils",
-        "BlobStoreTestUtils"
+        "BlobStoreTestUtils",
     ],
     jni_libs: ["liblmkhelper"],
     compile_multilib: "both",
@@ -60,6 +60,10 @@
 genrule {
     name: "statslog-statsd-cts-java-gen",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --java $(out) --module cts --javaPackage com.android.server.cts.device.statsd --javaClass StatsLogStatsdCts",
+    cmd: "$(location stats-log-api-gen) " +
+        "--java $(out) " +
+        "--module cts " +
+        "--javaPackage com.android.server.cts.device.statsd " +
+        "--javaClass StatsLogStatsdCts",
     out: ["com/android/server/cts/device/statsd/StatsLogStatsdCts.java"],
 }
diff --git a/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/RestrictedPermissionTests.java b/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/RestrictedPermissionTests.java
new file mode 100644
index 0000000..7896e5b
--- /dev/null
+++ b/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/RestrictedPermissionTests.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.device.statsd;
+
+import static org.junit.Assert.fail;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Build, install and run tests with following command:
+ * atest CtsStatsdHostTestCases
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RestrictedPermissionTests {
+
+    /**
+     * Verify that the {@link android.Manifest.permission#READ_RESTRICTED_STATS}
+     * permission is only held by at most one package.
+     */
+    @Test
+    @CddTest(requirements={"9.8.17/C-0-1"})
+    public void testReadRestrictedStatsPermission() throws Exception {
+        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+        final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[]{
+                android.Manifest.permission.READ_RESTRICTED_STATS
+        }, PackageManager.MATCH_ALL);
+
+        int count = 0;
+        String pkgNames = "";
+        for (PackageInfo pkg : holding) {
+            int uid = pm.getApplicationInfo(pkg.packageName, 0).uid;
+            if (UserHandle.isApp(uid)) {
+                pkgNames += pkg.packageName + "\n";
+                count++;
+            }
+        }
+        if (count > 1) {
+            fail("Only one app may hold the READ_RESTRICTED_STATS permission; found packages: \n"
+                    + pkgNames);
+        }
+    }
+}
diff --git a/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdStressLogging.java b/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdStressLogging.java
new file mode 100644
index 0000000..62dfe78
--- /dev/null
+++ b/tests/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdStressLogging.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsd;
+
+import android.os.SystemClock;
+import android.util.StatsLog;
+
+import org.junit.Test;
+
+public class StatsdStressLogging {
+    private static final int EVENT_STORM_ATOMS_COUNT = 100000;
+
+    /** Tests that logging many atoms back to back leads to socket overflow and data loss. */
+    @Test
+    public void testLogAtomsBackToBack() throws Exception {
+        // logging back to back many atoms to force socket overflow
+        logAtoms(EVENT_STORM_ATOMS_COUNT);
+
+        // Using sleep to allow bypass libstatsocket dumpAtomsLossStats() cooldown timer
+        SystemClock.sleep(100);
+
+        // Try to log atoms into socket successfully to trigger libstatsocket dumpAtomsLossStats()
+        logAtoms(1);
+    }
+
+    private void logAtoms(int iterations) {
+        // single atom logging takes ~2us excluding JNI interactions
+        for (int i = 0; i < iterations; i++) {
+            StatsLog.logStart(i);
+            StatsLog.logStop(i);
+        }
+    }
+}
diff --git a/tests/src/android/cts/statsd/alarm/AlarmTests.java b/tests/src/android/cts/statsd/alarm/AlarmTests.java
index 032297e..0f49e9a 100644
--- a/tests/src/android/cts/statsd/alarm/AlarmTests.java
+++ b/tests/src/android/cts/statsd/alarm/AlarmTests.java
@@ -17,21 +17,29 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.AtomTestCase;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
-import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.Alarm;
 import com.android.internal.os.StatsdConfigProto.IncidentdDetails;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.internal.os.StatsdConfigProto.Subscription;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
-import java.util.List;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 
 /**
  * Statsd Anomaly Detection tests.
  */
-public class AlarmTests extends AtomTestCase {
+public class AlarmTests extends DeviceTestCase implements IBuildReceiver {
 
     private static final String TAG = "Statsd.AnomalyDetectionTests";
 
@@ -42,36 +50,61 @@
     private static final int SUBSCRIPTION_ID_INCIDENTD = 41;
     private static final int INCIDENTD_SECTION = -1;
 
+    private IBuildInfo mCtsBuild;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
         if (!INCIDENTD_TESTS_ENABLED) {
             CLog.w(TAG, TAG + " alarm tests are disabled by a flag. Change flag to true to run");
         }
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
     }
 
     public void testAlarm() throws Exception {
         StatsdConfig.Builder config = getBaseConfig();
-        turnScreenOn();
-        uploadConfig(config);
+        DeviceUtils.turnScreenOn(getDevice());
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        String markTime = getCurrentLogcatDate();
-        Thread.sleep(9_000);
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        RunUtil.getDefault().sleep(9_000);
 
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isTrue();
+        }
     }
 
 
     private final StatsdConfig.Builder getBaseConfig() throws Exception {
-      return createConfigBuilder()
-          .addAlarm(Alarm.newBuilder().setId(ALARM_ID).setOffsetMillis(2).setPeriodMillis(
-              5_000) // every 5 seconds.
-              )
-          .addSubscription(Subscription.newBuilder()
-                               .setId(SUBSCRIPTION_ID_INCIDENTD)
-                               .setRuleType(Subscription.RuleType.ALARM)
-                               .setRuleId(ALARM_ID)
-                               .setIncidentdDetails(
-                                   IncidentdDetails.newBuilder().addSection(INCIDENTD_SECTION)));
+        return ConfigUtils.createConfigBuilder(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
+                .addAlarm(Alarm.newBuilder()
+                        .setId(ALARM_ID)
+                        .setOffsetMillis(2)
+                        .setPeriodMillis(5_000) // every 5 seconds.
+                )
+                .addSubscription(Subscription.newBuilder()
+                        .setId(SUBSCRIPTION_ID_INCIDENTD)
+                        .setRuleType(Subscription.RuleType.ALARM)
+                        .setRuleId(ALARM_ID)
+                        .setIncidentdDetails(IncidentdDetails.newBuilder()
+                                .addSection(INCIDENTD_SECTION)));
     }
 }
diff --git a/tests/src/android/cts/statsd/alert/AnomalyDetectionTests.java b/tests/src/android/cts/statsd/alert/AnomalyDetectionTests.java
index ea47cc3..74fe1b5 100644
--- a/tests/src/android/cts/statsd/alert/AnomalyDetectionTests.java
+++ b/tests/src/android/cts/statsd/alert/AnomalyDetectionTests.java
@@ -18,7 +18,11 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.cts.statsd.atom.AtomTestCase;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.Alert;
@@ -38,13 +42,30 @@
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.DebugElapsedClock;
 import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.log.LogUtil;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.RunUtil;
+
+import com.google.protobuf.ByteString;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.List;
+import java.util.Random;
+
+import perfetto.protos.PerfettoConfig.DataSourceConfig;
+import perfetto.protos.PerfettoConfig.FtraceConfig;
+import perfetto.protos.PerfettoConfig.TraceConfig;
 
 /**
  * Statsd Anomaly Detection tests.
  */
-public class AnomalyDetectionTests extends AtomTestCase {
+public class AnomalyDetectionTests extends DeviceTestCase implements IBuildReceiver {
 
     private static final String TAG = "Statsd.AnomalyDetectionTests";
 
@@ -53,6 +74,8 @@
 
     private static final int WAIT_AFTER_BREADCRUMB_MS = 2000;
 
+    private static final int SHELL_UID = 2000;
+
     // Config constants
     private static final int APP_BREADCRUMB_REPORTED_MATCH_START_ID = 1;
     private static final int APP_BREADCRUMB_REPORTED_MATCH_STOP_ID = 2;
@@ -66,14 +89,22 @@
 
     private boolean defaultSystemTracingConfigurationHasChanged = false;
 
+    private IBuildInfo mCtsBuild;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
         if (!INCIDENTD_TESTS_ENABLED) {
             CLog.w(TAG, TAG + " anomaly tests are disabled by a flag. Change flag to true to run");
         }
         if (PERFETTO_TESTS_ENABLED) {
-            // Default Android configuration can only change for device type that doesn't require SystemTracingEnabled
+            // Default Android configuration can only change for device type that doesn't require
+            // SystemTracingEnabled
             // by default in CDD.
             String chars = getDevice().getProperty("ro.build.characteristics");
             if (!isSystemTracingEnabled() && chars.contains("automotive")) {
@@ -81,11 +112,20 @@
                 defaultSystemTracingConfigurationHasChanged = true;
             }
         }
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
     }
 
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         if (PERFETTO_TESTS_ENABLED) {
             // Disable SystemTracing if previously enabled at test setUp()
             if (defaultSystemTracingConfigurationHasChanged) {
@@ -95,11 +135,12 @@
             final long deadLine = System.currentTimeMillis() + 10000;
             while (isSystemTracingEnabled()) {
                 if (System.currentTimeMillis() > deadLine) {
-                    CLog.w("/sys/kernel/debug/tracing/tracing_on is still 1 after 10 secs : " + isSystemTracingEnabled());
+                    CLog.w("/sys/kernel/debug/tracing/tracing_on is still 1 after 10 secs : "
+                            + isSystemTracingEnabled());
                     break;
                 }
                 CLog.d("Waiting to finish collecting traces. ");
-                Thread.sleep(WAIT_TIME_SHORT);
+                RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
             }
         }
     }
@@ -120,34 +161,50 @@
                                 )
                         )
                 );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        String markTime = getCurrentLogcatDate();
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
         // count(label=6) -> 1 (not an anomaly, since not "greater than 2")
-        doAppBreadcrumbReportedStart(6);
-        Thread.sleep(500);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 6);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isFalse();
+        }
 
         // count(label=6) -> 2 (not an anomaly, since not "greater than 2")
-        doAppBreadcrumbReportedStart(6);
-        Thread.sleep(500);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 6);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isFalse();
+        }
 
         // count(label=12) -> 1 (not an anomaly, since not "greater than 2")
-        doAppBreadcrumbReportedStart(12);
-        Thread.sleep(1000);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 12);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isFalse();
+        }
 
-        doAppBreadcrumbReportedStart(6); // count(label=6) -> 3 (anomaly, since "greater than 2"!)
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                6); // count(label=6) -> 3 (anomaly, since "greater than 2"!)
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
 
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isTrue();
+        }
     }
 
     // Tests that anomaly detection for duration works.
@@ -169,49 +226,64 @@
                                         .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
                                 )
                         );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
         // Since timing is crucial and checking logcat for incidentd is slow, we don't test for it.
 
         // Test that alarm doesn't fire early.
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(6_000);  // Recorded duration at end: 6s
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(6_000);  // Recorded duration at end: 6s
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
 
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(4_000);  // Recorded duration at end: 6s
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        RunUtil.getDefault().sleep(4_000);  // Recorded duration at end: 6s
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
 
         // Test that alarm does fire when it is supposed to (after 4s, plus up to 5s alarm delay).
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(9_000);  // Recorded duration at end: 13s
-        doAppBreadcrumbReported(2);
-        List<EventMetricData> data = getEventMetricDataList();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(9_000);  // Recorded duration at end: 13s
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 2);
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
 
         // Now test that the refractory period is obeyed.
-        markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStop(1);
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(3_000);  // Recorded duration at end: 13s
+        markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(3_000);  // Recorded duration at end: 13s
         // NB: the previous getEventMetricDataList also removes the report, so size is back to 0.
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
 
         // Test that detection works again after refractory period finishes.
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(8_000);  // Recorded duration at end: 9s
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(15_000);  // Recorded duration at end: 15s
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        RunUtil.getDefault().sleep(8_000);  // Recorded duration at end: 9s
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(15_000);  // Recorded duration at end: 15s
         // We can do an incidentd test now that all the timing issues are done.
-        doAppBreadcrumbReported(2);
-        data = getEventMetricDataList();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 2);
+        data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isTrue();
+        }
 
-        doAppBreadcrumbReportedStop(1);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
     }
 
     // Tests that anomaly detection for duration works even when the alarm fires too late.
@@ -233,24 +305,29 @@
                                         .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
                                 )
                         );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(5_000);
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(2_000);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(5_000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        RunUtil.getDefault().sleep(2_000);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
 
         // Test that alarm does fire when it is supposed to.
         // The anomaly occurs in 1s, but alarms won't fire that quickly.
         // It is likely that the alarm will only fire after this period is already over, but the
         // anomaly should nonetheless be detected when the event stops.
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(1_200);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(1_200);
         // Anomaly should be detected here if the alarm didn't fire yet.
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(200);
-        List<EventMetricData> data = getEventMetricDataList();
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        RunUtil.getDefault().sleep(200);
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         if (data.size() == 2) {
             // Although we expect that the alarm won't fire, we certainly cannot demand that.
             CLog.w(TAG, "The anomaly was detected twice. Presumably the alarm did manage to fire.");
@@ -274,28 +351,37 @@
                         )
 
                 );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                6); // value = 6, which is NOT > trigger
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isFalse();
+        }
 
-        doAppBreadcrumbReportedStart(14); // value = 14 > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                14); // value = 14 > trigger
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
 
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isTrue();
+        }
     }
 
     // Test that anomaly detection integrates with perfetto properly.
     public void testPerfetto() throws Exception {
         String chars = getDevice().getProperty("ro.build.characteristics");
         if (chars.contains("watch")) {
-                return;
+            return;
         }
 
         if (PERFETTO_TESTS_ENABLED) resetPerfettoGuardrails();
@@ -320,32 +406,38 @@
                         )
 
                 );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                6); // value = 6, which is NOT > trigger
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
         if (PERFETTO_TESTS_ENABLED) assertThat(isSystemTracingEnabled()).isFalse();
 
-        doAppBreadcrumbReportedStart(14); // value = 14 > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                14); // value = 14 > trigger
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
 
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
 
-        // Pool a few times to allow for statsd <-> traced <-> traced_probes communication to happen.
+        // Pool a few times to allow for statsd <-> traced <-> traced_probes communication to
+        // happen.
         if (PERFETTO_TESTS_ENABLED) {
-                boolean tracingEnabled = false;
-                for (int i = 0; i < 5; i++) {
-                        if (isSystemTracingEnabled()) {
-                                tracingEnabled = true;
-                                break;
-                        }
-                        Thread.sleep(1000);
+            boolean tracingEnabled = false;
+            for (int i = 0; i < 5; i++) {
+                if (isSystemTracingEnabled()) {
+                    tracingEnabled = true;
+                    break;
                 }
-                assertThat(tracingEnabled).isTrue();
+                RunUtil.getDefault().sleep(1000);
+            }
+            assertThat(tracingEnabled).isTrue();
         }
     }
 
@@ -365,22 +457,32 @@
                                 )
                         )
                 );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        String markTime = getCurrentLogcatDate();
-        doAppBreadcrumbReportedStart(6); // gauge = 6, which is NOT > trigger
-        Thread.sleep(Math.max(WAIT_AFTER_BREADCRUMB_MS, 1_100)); // Must be >1s to push next bucket.
-        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
+        String markTime = MetricsUtils.getCurrentLogcatDate(getDevice());
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                6); // gauge = 6, which is NOT > trigger
+        RunUtil.getDefault().sleep(
+                Math.max(WAIT_AFTER_BREADCRUMB_MS, 1_100)); // Must be >1s to push next bucket.
+        assertWithMessage("Premature anomaly").that(
+                ReportUtils.getEventMetricDataList(getDevice())).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isFalse();
+        }
 
         // We waited for >1s above, so we are now in the next bucket (which is essential).
-        doAppBreadcrumbReportedStart(14); // gauge = 14 > trigger
-        Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(),
+                14); // gauge = 14 > trigger
+        RunUtil.getDefault().sleep(WAIT_AFTER_BREADCRUMB_MS);
 
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertWithMessage("Expected anomaly").that(data).hasSize(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
-        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
+        if (INCIDENTD_TESTS_ENABLED) {
+            assertThat(MetricsUtils.didIncidentdFireSince(getDevice(), markTime)).isTrue();
+        }
     }
 
     // Test that anomaly detection for pulled metrics work.
@@ -410,73 +512,165 @@
                                 )
                         )
                 );
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
 
-        Thread.sleep(6_000); // Wait long enough to ensure AlarmManager signals >= 1 pull
+        RunUtil.getDefault().sleep(
+                6_000); // Wait long enough to ensure AlarmManager signals >= 1 pull
 
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         assertThat(data.size()).isEqualTo(1);
         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
     }
 
 
     private final StatsdConfig.Builder getBaseConfig(int numBuckets,
-                                                     int refractorySecs,
-                                                     long triggerIfSumGt) throws Exception {
-      return createConfigBuilder()
-          // Items of relevance for detecting the anomaly:
-          .addAtomMatcher(
-              StatsdConfigProto.AtomMatcher.newBuilder()
-                  .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
-                  .setSimpleAtomMatcher(
-                      StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                          .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                          // Event only when the uid is this app's uid.
-                          .addFieldValueMatcher(createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
-                                                    .setEqInt(getHostUid()))
-                          .addFieldValueMatcher(
-                              createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                  .setEqInt(AppBreadcrumbReported.State.START.ordinal()))))
-          .addAtomMatcher(
-              StatsdConfigProto.AtomMatcher.newBuilder()
-                  .setId(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
-                  .setSimpleAtomMatcher(
-                      StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                          .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                          // Event only when the uid is this app's uid.
-                          .addFieldValueMatcher(createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
-                                                    .setEqInt(getHostUid()))
-                          .addFieldValueMatcher(
-                              createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                  .setEqInt(AppBreadcrumbReported.State.STOP.ordinal()))))
-          .addAlert(Alert.newBuilder()
+            int refractorySecs,
+            long triggerIfSumGt) throws Exception {
+        return ConfigUtils.createConfigBuilder(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
+                // Items of relevance for detecting the anomaly:
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) // Event
+                                // only when the uid is this app's uid.
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
+                                        .setEqInt(SHELL_UID))
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                        .setEqInt(AppBreadcrumbReported.State.START.getNumber()))))
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                // Event only when the uid is this app's uid.
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
+                                        .setEqInt(SHELL_UID))
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                        .setEqInt(AppBreadcrumbReported.State.STOP.getNumber()))))
+                .addAlert(Alert.newBuilder()
                         .setId(ALERT_ID)
                         .setMetricId(METRIC_ID) // The metric itself must yet be added by the test.
                         .setNumBuckets(numBuckets)
                         .setRefractoryPeriodSecs(refractorySecs)
                         .setTriggerIfSumGt(triggerIfSumGt))
-          .addSubscription(
-              Subscription.newBuilder()
-                  .setId(SUBSCRIPTION_ID_INCIDENTD)
-                  .setRuleType(Subscription.RuleType.ALERT)
-                  .setRuleId(ALERT_ID)
-                  .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(INCIDENTD_SECTION)))
-          // We want to trigger anomalies on METRIC_ID, but don't want the actual data.
-          .addNoReportMetric(METRIC_ID)
+                .addSubscription(Subscription.newBuilder()
+                        .setId(SUBSCRIPTION_ID_INCIDENTD)
+                        .setRuleType(Subscription.RuleType.ALERT)
+                        .setRuleId(ALERT_ID)
+                        .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(
+                                INCIDENTD_SECTION)))
+                // We want to trigger anomalies on METRIC_ID, but don't want the actual data.
+                .addNoReportMetric(METRIC_ID)
 
-          // Items of relevance to reporting the anomaly (we do want this data):
-          .addAtomMatcher(
-              StatsdConfigProto.AtomMatcher.newBuilder()
-                  .setId(ANOMALY_DETECT_MATCH_ID)
-                  .setSimpleAtomMatcher(
-                      StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                          .setAtomId(Atom.ANOMALY_DETECTED_FIELD_NUMBER)
-                          .addFieldValueMatcher(createFvm(AnomalyDetected.CONFIG_UID_FIELD_NUMBER)
-                                                    .setEqInt(getHostUid()))
-                          .addFieldValueMatcher(createFvm(AnomalyDetected.CONFIG_ID_FIELD_NUMBER)
-                                                    .setEqInt(CONFIG_ID))))
-          .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
-                              .setId(ANOMALY_EVENT_ID)
-                              .setWhat(ANOMALY_DETECT_MATCH_ID));
+                // Items of relevance to reporting the anomaly (we do want this data):
+                .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+                        .setId(ANOMALY_DETECT_MATCH_ID)
+                        .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.ANOMALY_DETECTED_FIELD_NUMBER)
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AnomalyDetected.CONFIG_UID_FIELD_NUMBER)
+                                        .setEqInt(SHELL_UID))
+                                .addFieldValueMatcher(ConfigUtils
+                                        .createFvm(AnomalyDetected.CONFIG_ID_FIELD_NUMBER)
+                                        .setEqInt(ConfigUtils.CONFIG_ID))))
+                .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
+                        .setId(ANOMALY_EVENT_ID)
+                        .setWhat(ANOMALY_DETECT_MATCH_ID));
+    }
+
+    /**
+     * Determines whether perfetto enabled the kernel ftrace tracer.
+     */
+    protected boolean isSystemTracingEnabled() throws Exception {
+        final String traceFsPath = "/sys/kernel/tracing/tracing_on";
+        String tracing_on = probe(traceFsPath);
+        if (tracing_on.startsWith("0")) return false;
+        if (tracing_on.startsWith("1")) return true;
+
+        // fallback to debugfs
+        LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
+                tracing_on);
+
+        final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
+        tracing_on = probe(debugFsPath);
+        if (tracing_on.startsWith("0")) return false;
+        if (tracing_on.startsWith("1")) return true;
+        throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
+    }
+
+    private String probe(String path) throws Exception {
+        return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
+                + " cat " + path + " ; else echo -1 ; fi");
+    }
+
+    protected void enableSystemTracing() throws Exception {
+        getDevice().executeShellCommand("setprop persist.traced.enable 1");
+    }
+
+    protected void disableSystemTracing() throws Exception {
+        getDevice().executeShellCommand("setprop persist.traced.enable 0");
+    }
+
+    /**
+     * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
+     * run too close of for too many times and hits the upload limit.
+     */
+    private void resetPerfettoGuardrails() throws Exception {
+        final String cmd = "perfetto --reset-guardrails";
+        CommandResult cr = getDevice().executeShellV2Command(cmd);
+        if (cr.getStatus() != CommandStatus.SUCCESS) {
+            throw new Exception(
+                    String.format("Error while executing %s: %s %s", cmd, cr.getStdout(),
+                            cr.getStderr()));
+        }
+    }
+
+    /**
+     * Returns a protobuf-encoded perfetto config that enables the kernel
+     * ftrace tracer with sched_switch for 10 seconds.
+     */
+    private ByteString getPerfettoConfig() {
+        TraceConfig.Builder builder = TraceConfig.newBuilder();
+
+        TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig
+                .newBuilder()
+                .setSizeKb(128)
+                .build();
+        builder.addBuffers(buffer);
+
+        FtraceConfig ftraceConfig = FtraceConfig.newBuilder()
+                .addFtraceEvents("sched/sched_switch")
+                .build();
+        DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
+                .setName("linux.ftrace")
+                .setTargetBuffer(0)
+                .setFtraceConfig(ftraceConfig)
+                .build();
+        TraceConfig.DataSource dataSource = TraceConfig.DataSource
+                .newBuilder()
+                .setConfig(dataSourceConfig)
+                .build();
+        builder.addDataSources(dataSource);
+
+        builder.setDurationMs(10000);
+        builder.setAllowUserBuildTracing(true);
+
+        TraceConfig.IncidentReportConfig incident = TraceConfig.IncidentReportConfig
+                .newBuilder()
+                .setSkipIncidentd(true)
+                .build();
+        builder.setIncidentReportConfig(incident);
+
+        // To avoid being hit with guardrails firing in multiple test runs back
+        // to back, we set a unique session key for each config.
+        Random random = new Random();
+        StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-");
+        sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
+        builder.setUniqueSessionName(sessionNameBuilder.toString());
+
+        return builder.build().toByteString();
     }
 }
diff --git a/tests/src/android/cts/statsd/apex/BootstrapApexTests.java b/tests/src/android/cts/statsd/apex/BootstrapApexTests.java
index ab6093f..2db635a 100644
--- a/tests/src/android/cts/statsd/apex/BootstrapApexTests.java
+++ b/tests/src/android/cts/statsd/apex/BootstrapApexTests.java
@@ -18,11 +18,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.BaseTestCase;
 import com.android.apex.ApexInfo;
 import com.android.apex.XmlParser;
 import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.util.List;
@@ -30,7 +31,7 @@
 /**
  * Verify statsd is not in the bootstrap apexes
  */
-public class BootstrapApexTests extends BaseTestCase {
+public class BootstrapApexTests extends DeviceTestCase {
     private static final String TAG = "Statsd.BootstrapApexTests";
 
     // Constants for the paths to apex-info-list.xml
@@ -39,6 +40,7 @@
     // - legacy location
     private static final String BOOTSTRAP_APEX_FILE2 = "/apex/.bootstrap-apex-info-list.xml";
 
+
     private boolean sdkLevelAtLeast(int sdkLevel, String codename) throws Exception {
         return ApiLevelUtil.isAtLeast(getDevice(), sdkLevel)
                 || ApiLevelUtil.codenameEquals(getDevice(), codename);
diff --git a/tests/src/android/cts/statsd/atom/AtomTestCase.java b/tests/src/android/cts/statsd/atom/AtomTestCase.java
deleted file mode 100644
index fc37543..0000000
--- a/tests/src/android/cts/statsd/atom/AtomTestCase.java
+++ /dev/null
@@ -1,1263 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.atom;
-
-import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_APK;
-import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.BatteryStatsProto;
-import android.os.StatsDataDumpProto;
-import android.service.battery.BatteryServiceDumpProto;
-import android.service.batterystats.BatteryStatsServiceDumpProto;
-import android.service.procstats.ProcessStatsServiceDumpProto;
-
-import com.android.annotations.Nullable;
-import com.android.internal.os.StatsdConfigProto.AtomMatcher;
-import com.android.internal.os.StatsdConfigProto.EventMetric;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
-import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
-import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
-import com.android.internal.os.StatsdConfigProto.SimplePredicate;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.ProcessStatsPackageProto;
-import com.android.os.AtomsProto.ProcessStatsProto;
-import com.android.os.AtomsProto.ProcessStatsStateProto;
-import com.android.os.StatsLog;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
-import com.android.os.StatsLog.CountMetricData;
-import com.android.os.StatsLog.DurationMetricData;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.os.StatsLog.GaugeBucketInfo;
-import com.android.os.StatsLog.GaugeMetricData;
-import com.android.os.StatsLog.StatsLogReport;
-import com.android.os.StatsLog.StatsLogReport.GaugeMetricDataWrapper;
-import com.android.os.StatsLog.ValueMetricData;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.Pair;
-
-import com.google.common.collect.Range;
-import com.google.common.io.Files;
-import com.google.protobuf.ByteString;
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Random;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.function.Function;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import perfetto.protos.PerfettoConfig.DataSourceConfig;
-import perfetto.protos.PerfettoConfig.FtraceConfig;
-import perfetto.protos.PerfettoConfig.TraceConfig;
-
-/**
- * Base class for testing Statsd atoms.
- * Validates reporting of statsd logging based on different events
- */
-public class AtomTestCase extends BaseTestCase {
-
-    /**
-     * Run tests that are optional; they are not valid CTS tests per se, since not all devices can
-     * be expected to pass them, but can be run, if desired, to ensure they work when appropriate.
-     */
-    public static final boolean OPTIONAL_TESTS_ENABLED = false;
-
-    public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
-    public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
-    public static final String DUMP_BATTERY_CMD = "dumpsys battery";
-    public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats";
-    public static final String DUMPSYS_STATS_CMD = "dumpsys stats";
-    public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
-    public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
-    /** ID of the config, which evaluates to -1572883457. */
-    public static final long CONFIG_ID = "cts_config".hashCode();
-
-    public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
-    public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
-    public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
-    public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
-    public static final String FEATURE_CAMERA = "android.hardware.camera";
-    public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
-    public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
-    public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
-    public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
-    public static final String FEATURE_PC = "android.hardware.type.pc";
-    public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
-    public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
-    public static final String FEATURE_WATCH = "android.hardware.type.watch";
-    public static final String FEATURE_WIFI = "android.hardware.wifi";
-    public static final String FEATURE_INCREMENTAL_DELIVERY =
-            "android.software.incremental_delivery";
-
-    public static final int SHELL_UID = 2000;
-
-    // Telephony phone types
-    public static final int PHONE_TYPE_GSM = 1;
-    public static final int PHONE_TYPE_CDMA = 2;
-    public static final int PHONE_TYPE_CDMA_LTE = 6;
-
-    protected static final int WAIT_TIME_SHORT = 500;
-    protected static final int WAIT_TIME_LONG = 2_000;
-
-    protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
-    protected static final long SCREEN_STATE_POLLING_INTERVAL = 500;
-
-    protected static final long NS_PER_SEC = (long) 1E+9;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // Uninstall to clear the history in case it's still on the device.
-        removeConfig(CONFIG_ID);
-        getReportList(); // Clears data.
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        removeConfig(CONFIG_ID);
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        super.tearDown();
-    }
-
-    /**
-     * Determines whether logcat indicates that incidentd fired since the given device date.
-     */
-    protected boolean didIncidentdFireSince(String date) throws Exception {
-        final String INCIDENTD_TAG = "incidentd";
-        final String INCIDENTD_STARTED_STRING = "reportIncident";
-        // TODO: Do something more robust than this in case of delayed logging.
-        Thread.sleep(1000);
-        String log = getLogcatSince(date, String.format(
-                "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
-        return log.contains(INCIDENTD_STARTED_STRING);
-    }
-
-    protected boolean checkDeviceFor(String methodName) throws Exception {
-        try {
-            installPackage(DEVICE_SIDE_TEST_APK, true);
-            runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName);
-            // Test passes, meaning that the answer is true.
-            LogUtil.CLog.d(methodName + "() indicates true.");
-            return true;
-        } catch (AssertionError e) {
-            // Method is designed to fail if the answer is false.
-            LogUtil.CLog.d(methodName + "() indicates false.");
-            return false;
-        }
-    }
-
-    /**
-     * Returns a protobuf-encoded perfetto config that enables the kernel
-     * ftrace tracer with sched_switch for 10 seconds.
-     */
-    protected ByteString getPerfettoConfig() {
-        TraceConfig.Builder builder = TraceConfig.newBuilder();
-
-        TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig
-            .newBuilder()
-            .setSizeKb(128)
-            .build();
-        builder.addBuffers(buffer);
-
-        FtraceConfig ftraceConfig = FtraceConfig.newBuilder()
-            .addFtraceEvents("sched/sched_switch")
-            .build();
-        DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
-            .setName("linux.ftrace")
-            .setTargetBuffer(0)
-            .setFtraceConfig(ftraceConfig)
-            .build();
-        TraceConfig.DataSource dataSource = TraceConfig.DataSource
-            .newBuilder()
-            .setConfig(dataSourceConfig)
-            .build();
-        builder.addDataSources(dataSource);
-
-        builder.setDurationMs(10000);
-        builder.setAllowUserBuildTracing(true);
-
-        TraceConfig.IncidentReportConfig incident = TraceConfig.IncidentReportConfig
-            .newBuilder()
-            .setDestinationPackage("foo.bar.baz")
-            .build();
-        builder.setIncidentReportConfig(incident);
-
-        // To avoid being hit with guardrails firing in multiple test runs back
-        // to back, we set a unique session key for each config.
-        Random random = new Random();
-        StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-");
-        sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
-        builder.setUniqueSessionName(sessionNameBuilder.toString());
-
-        return builder.build().toByteString();
-    }
-
-    /**
-     * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
-     * run too close of for too many times and hits the upload limit.
-     */
-    protected void resetPerfettoGuardrails() throws Exception {
-        final String cmd = "perfetto --reset-guardrails";
-        CommandResult cr = getDevice().executeShellV2Command(cmd);
-        if (cr.getStatus() != CommandStatus.SUCCESS)
-            throw new Exception(String.format("Error while executing %s: %s %s", cmd, cr.getStdout(), cr.getStderr()));
-    }
-
-    private String probe(String path) throws Exception {
-        return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
-                + " cat " + path + " ; else echo -1 ; fi");
-    }
-
-    protected void enableSystemTracing() throws Exception {
-        getDevice().executeShellCommand("setprop persist.traced.enable 1");
-    }
-
-    protected void disableSystemTracing() throws Exception {
-        getDevice().executeShellCommand("setprop persist.traced.enable 0");
-    }
-
-    /**
-     * Determines whether perfetto enabled the kernel ftrace tracer.
-     */
-    protected boolean isSystemTracingEnabled() throws Exception {
-        final String traceFsPath = "/sys/kernel/tracing/tracing_on";
-        String tracing_on = probe(traceFsPath);
-        if (tracing_on.startsWith("0")) return false;
-        if (tracing_on.startsWith("1")) return true;
-
-        // fallback to debugfs
-        LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
-                tracing_on);
-
-        final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
-        tracing_on = probe(debugFsPath);
-        if (tracing_on.startsWith("0")) return false;
-        if (tracing_on.startsWith("1")) return true;
-        throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
-    }
-
-    protected static StatsdConfig.Builder createConfigBuilder() {
-      return StatsdConfig.newBuilder()
-          .setId(CONFIG_ID)
-          .addAllowedLogSource("AID_SYSTEM")
-          .addAllowedLogSource("AID_BLUETOOTH")
-          // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
-          .addAllowedLogSource("com.android.bluetooth")
-          .addAllowedLogSource("AID_LMKD")
-          .addAllowedLogSource("AID_RADIO")
-          .addAllowedLogSource("AID_ROOT")
-          .addAllowedLogSource("AID_STATSD")
-          .addAllowedLogSource("com.android.systemui")
-          .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE)
-          .addDefaultPullPackages("AID_RADIO")
-          .addDefaultPullPackages("AID_SYSTEM")
-          .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
-    }
-
-    protected void createAndUploadConfig(int atomTag) throws Exception {
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, atomTag);
-        uploadConfig(conf);
-    }
-
-    protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
-        uploadConfig(config.build());
-    }
-
-    protected void uploadConfig(StatsdConfig config) throws Exception {
-        LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
-        File configFile = File.createTempFile("statsdconfig", ".config");
-        configFile.deleteOnExit();
-        Files.write(config.toByteArray(), configFile);
-        String remotePath = "/data/local/tmp/" + configFile.getName();
-        getDevice().pushFile(configFile, remotePath);
-        getDevice().executeShellCommand(
-                String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
-                        String.valueOf(SHELL_UID), String.valueOf(CONFIG_ID)));
-        getDevice().executeShellCommand("rm " + remotePath);
-    }
-
-    protected void removeConfig(long configId) throws Exception {
-        getDevice().executeShellCommand(
-                String.join(" ", REMOVE_CONFIG_CMD,
-                        String.valueOf(SHELL_UID), String.valueOf(configId)));
-    }
-
-    /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */
-    protected List<EventMetricData> getEventMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        return getEventMetricDataList(reportList);
-    }
-
-    /**
-     *  Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
-     */
-    protected List<ConfigMetricsReport> getSortedConfigMetricsReports(
-            ConfigMetricsReportList configMetricsReportList) {
-        return configMetricsReportList.getReportsList().stream()
-                .sorted(Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos))
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
-     * contain a single report).
-     */
-    protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
-            throws Exception {
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<EventMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-          for (EventMetricData metricData :
-               metric.getEventMetrics().getDataList()) {
-            if (metricData.hasAtom()) {
-              data.add(metricData);
-            } else {
-              data.addAll(backfillAggregatedAtomsInEventMetric(metricData));
-            }
-          }
-        }
-        data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
-
-        LogUtil.CLog.d("Get EventMetricDataList as following:\n");
-        for (EventMetricData d : data) {
-            LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
-        }
-        return data;
-    }
-
-    protected List<Atom> getGaugeMetricDataList() throws Exception {
-        return getGaugeMetricDataList(/*checkTimestampTruncated=*/false);
-    }
-
-    protected List<Atom> getGaugeMetricDataList(boolean checkTimestampTruncated) throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-
-        // only config
-        ConfigMetricsReport report = reportList.getReports(0);
-        assertThat(report.getMetricsCount()).isEqualTo(1);
-
-        List<Atom> data = new ArrayList<>();
-        for (GaugeMetricData gaugeMetricData :
-                report.getMetrics(0).getGaugeMetrics().getDataList()) {
-            assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1);
-            GaugeBucketInfo bucketInfo = gaugeMetricData.getBucketInfo(0);
-            if (bucketInfo.getAtomCount() != 0) {
-                for (Atom atom : bucketInfo.getAtomList()) {
-                    data.add(atom);
-                }
-            } else {
-                backFillGaugeBucketAtoms(bucketInfo.getAggregatedAtomInfoList());
-            }
-            if (checkTimestampTruncated) {
-                for (long timestampNs : bucketInfo.getElapsedTimestampNanosList()) {
-                    assertTimestampIsTruncated(timestampNs);
-                }
-            }
-        }
-
-        LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
-        for (Atom d : data) {
-            LogUtil.CLog.d("Atom:\n" + d.toString());
-        }
-        return data;
-    }
-
-    private List<Atom> backFillGaugeBucketAtoms(
-            List<StatsLog.AggregatedAtomInfo> atomInfoList) {
-        List<Pair<Atom, Long>> atomTimestamp = new ArrayList<>();
-        for (StatsLog.AggregatedAtomInfo atomInfo : atomInfoList) {
-            for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) {
-                atomTimestamp.add(Pair.create(atomInfo.getAtom(), timestampNs));
-            }
-        }
-        atomTimestamp.sort(Comparator.comparing(o -> o.second));
-        return atomTimestamp.stream().map(p -> p.first).collect(Collectors.toList());
-    }
-
-    protected GaugeMetricDataWrapper backfillGaugeMetricData(GaugeMetricDataWrapper dataWrapper) {
-        GaugeMetricDataWrapper.Builder dataWrapperBuilder = dataWrapper.toBuilder();
-        List<GaugeMetricData> backfilledMetricData = new ArrayList<>();
-        for (GaugeMetricData gaugeMetricData : dataWrapperBuilder.getDataList()) {
-            GaugeMetricData.Builder gaugeMetricDataBuilder = gaugeMetricData.toBuilder();
-            List<GaugeBucketInfo> backfilledBuckets = new ArrayList<>();
-            for (GaugeBucketInfo bucketInfo : gaugeMetricData.getBucketInfoList()) {
-                backfilledBuckets.add(backfillGaugeBucket(bucketInfo.toBuilder()));
-            }
-            gaugeMetricDataBuilder.clearBucketInfo();
-            gaugeMetricDataBuilder.addAllBucketInfo(backfilledBuckets);
-            backfilledMetricData.add(gaugeMetricDataBuilder.build());
-        }
-        dataWrapperBuilder.clearData();
-        dataWrapperBuilder.addAllData(backfilledMetricData);
-        return dataWrapperBuilder.build();
-    }
-
-    private GaugeBucketInfo backfillGaugeBucket(GaugeBucketInfo.Builder bucketInfoBuilder) {
-        if (bucketInfoBuilder.getAtomCount() != 0) {
-            return bucketInfoBuilder.build();
-        }
-        List<Pair<Atom, Long>> atomTimestampData = new ArrayList<>();
-        for (StatsLog.AggregatedAtomInfo atomInfo : bucketInfoBuilder.getAggregatedAtomInfoList()) {
-            for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) {
-                atomTimestampData.add(Pair.create(atomInfo.getAtom(), timestampNs));
-            }
-        }
-        atomTimestampData.sort(Comparator.comparing(o -> o.second));
-        bucketInfoBuilder.clearAggregatedAtomInfo();
-        for (Pair<Atom, Long> atomTimestamp : atomTimestampData) {
-            bucketInfoBuilder.addAtom(atomTimestamp.first);
-            bucketInfoBuilder.addElapsedTimestampNanos(atomTimestamp.second);
-        }
-        return bucketInfoBuilder.build();
-    }
-
-    /**
-     * Gets the statsd report and extract duration metric data.
-     * Note that this also deletes that report from statsd.
-     */
-    protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<DurationMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-            data.addAll(metric.getDurationMetrics().getDataList());
-        }
-
-        LogUtil.CLog.d("Got DurationMetricDataList as following:\n");
-        for (DurationMetricData d : data) {
-            LogUtil.CLog.d("Duration " + d);
-        }
-        return data;
-    }
-
-    /**
-     * Gets the statsd report and extract count metric data.
-     * Note that this also deletes that report from statsd.
-     */
-    protected List<CountMetricData> getCountMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<CountMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-            data.addAll(metric.getCountMetrics().getDataList());
-        }
-
-        LogUtil.CLog.d("Got CountMetricDataList as following:\n");
-        for (CountMetricData d : data) {
-            LogUtil.CLog.d("Count " + d);
-        }
-        return data;
-    }
-
-    /**
-     * Gets the statsd report and extract value metric data.
-     * Note that this also deletes that report from statsd.
-     */
-    protected List<ValueMetricData> getValueMetricDataList() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        ConfigMetricsReport report = reportList.getReports(0);
-
-        List<ValueMetricData> data = new ArrayList<>();
-        for (StatsLogReport metric : report.getMetricsList()) {
-            data.addAll(metric.getValueMetrics().getDataList());
-        }
-
-        LogUtil.CLog.d("Got ValueMetricDataList as following:\n");
-        for (ValueMetricData d : data) {
-            LogUtil.CLog.d("Value " + d);
-        }
-        return data;
-    }
-
-    protected StatsLogReport getStatsLogReport() throws Exception {
-        ConfigMetricsReport report = getConfigMetricsReport();
-        assertThat(report.hasUidMap()).isTrue();
-        assertThat(report.getMetricsCount()).isEqualTo(1);
-        return report.getMetrics(0);
-    }
-
-    protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
-        ConfigMetricsReportList reportList = getReportList();
-        assertThat(reportList.getReportsCount()).isEqualTo(1);
-        return reportList.getReports(0);
-    }
-
-    /** Gets the statsd report. Note that this also deletes that report from statsd. */
-    protected ConfigMetricsReportList getReportList() throws Exception {
-        try {
-            ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
-                    String.join(" ", DUMP_REPORT_CMD, String.valueOf(SHELL_UID),
-                            String.valueOf(CONFIG_ID), "--include_current_bucket", "--proto"));
-            return reportList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to fetch and parse the statsd output report. "
-                    + "Perhaps there is not a valid statsd config for the requested "
-                    + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
-            throw (e);
-        }
-    }
-
-    protected BatteryStatsProto getBatteryStatsProto() throws Exception {
-        try {
-            BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(),
-                    String.join(" ", DUMP_BATTERYSTATS_CMD,
-                            "--proto")).getBatterystats();
-            LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString());
-            return batteryStatsProto;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump batterystats proto");
-            throw (e);
-        }
-    }
-
-    /** Gets reports from the statsd data incident section from the stats dumpsys. */
-    protected List<ConfigMetricsReportList> getReportsFromStatsDataDumpProto() throws Exception {
-        try {
-            StatsDataDumpProto statsProto = getDump(StatsDataDumpProto.parser(),
-                    String.join(" ", DUMPSYS_STATS_CMD, "--proto"));
-            // statsProto holds repeated bytes, which we must parse into ConfigMetricsReportLists.
-            List<ConfigMetricsReportList> reports
-                    = new ArrayList<>(statsProto.getConfigMetricsReportListCount());
-            for (ByteString reportListBytes : statsProto.getConfigMetricsReportListList()) {
-                reports.add(ConfigMetricsReportList.parseFrom(reportListBytes));
-            }
-            LogUtil.CLog.d("Got dumpsys stats output:\n " + reports.toString());
-            return reports;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dumpsys stats proto");
-            throw (e);
-        }
-    }
-
-    protected List<ProcessStatsProto> getProcStatsProto() throws Exception {
-        try {
-
-            List<ProcessStatsProto> processStatsProtoList =
-                new ArrayList<ProcessStatsProto>();
-            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
-                    ProcessStatsServiceDumpProto.parser(),
-                    String.join(" ", DUMP_PROCSTATS_CMD,
-                            "--proto")).getProcstatsNow();
-            for (android.service.procstats.ProcessStatsProto stats :
-                    sectionProto.getProcessStatsList()) {
-                ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom(
-                    stats.toByteArray());
-                processStatsProtoList.add(procStats);
-            }
-            LogUtil.CLog.d("Got procstats:\n ");
-            for (ProcessStatsProto processStatsProto : processStatsProtoList) {
-                LogUtil.CLog.d(processStatsProto.toString());
-            }
-            return processStatsProtoList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump procstats proto");
-            throw (e);
-        }
-    }
-
-    /*
-     * Get all procstats package data in proto
-     */
-    protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception {
-        try {
-            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
-                    ProcessStatsServiceDumpProto.parser(),
-                    String.join(" ", DUMP_PROCSTATS_CMD,
-                            "--proto")).getProcstatsOver24Hrs();
-            List<ProcessStatsPackageProto> processStatsProtoList =
-                new ArrayList<ProcessStatsPackageProto>();
-            for (android.service.procstats.ProcessStatsPackageProto pkgStast :
-                sectionProto.getPackageStatsList()) {
-              ProcessStatsPackageProto pkgAtom =
-                  ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray());
-                processStatsProtoList.add(pkgAtom);
-            }
-            LogUtil.CLog.d("Got procstats:\n ");
-            for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) {
-                LogUtil.CLog.d(processStatsProto.toString());
-            }
-            return processStatsProtoList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump procstats proto");
-            throw (e);
-        }
-    }
-
-    /*
-     * Get all processes' procstats statsd data in proto
-     */
-    protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd()
-            throws Exception {
-        try {
-            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
-                    android.service.procstats.ProcessStatsSectionProto.parser(),
-                    String.join(" ", DUMP_PROCSTATS_CMD,
-                            "--statsd"));
-            List<android.service.procstats.ProcessStatsProto> processStatsProtoList
-                    = sectionProto.getProcessStatsList();
-            LogUtil.CLog.d("Got procstats:\n ");
-            for (android.service.procstats.ProcessStatsProto processStatsProto
-                    : processStatsProtoList) {
-                LogUtil.CLog.d(processStatsProto.toString());
-            }
-            return processStatsProtoList;
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump procstats proto");
-            throw (e);
-        }
-    }
-
-    protected boolean hasBattery() throws Exception {
-        try {
-            BatteryServiceDumpProto batteryProto = getDump(BatteryServiceDumpProto.parser(),
-                    String.join(" ", DUMP_BATTERY_CMD, "--proto"));
-            LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
-            return batteryProto.getIsPresent();
-        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
-            LogUtil.CLog.e("Failed to dump batteryservice proto");
-            throw (e);
-        }
-    }
-
-    /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
-    protected static FieldValueMatcher.Builder createFvm(int field) {
-        return FieldValueMatcher.newBuilder().setField(field);
-    }
-
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
-        addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the given key.
-     *
-     * @param conf    configuration
-     * @param atomTag atom tag (from atoms.proto)
-     * @param fvm     FieldValueMatcher.Builder for the relevant key
-     */
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
-            FieldValueMatcher.Builder fvm)
-            throws Exception {
-        addAtomEvent(conf, atomTag, Arrays.asList(fvm));
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the given keys.
-     *
-     * @param conf   configuration
-     * @param atomId atom tag (from atoms.proto)
-     * @param fvms   list of FieldValueMatcher.Builders to attach to the atom. May be null.
-     */
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
-            List<FieldValueMatcher.Builder> fvms) throws Exception {
-
-        final String atomName = "Atom" + System.nanoTime();
-        final String eventName = "Event" + System.nanoTime();
-
-        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
-        if (fvms != null) {
-            for (FieldValueMatcher.Builder fvm : fvms) {
-                sam.addFieldValueMatcher(fvm);
-            }
-        }
-        conf.addAtomMatcher(AtomMatcher.newBuilder()
-                .setId(atomName.hashCode())
-                .setSimpleAtomMatcher(sam));
-        conf.addEventMetric(EventMetric.newBuilder()
-                .setId(eventName.hashCode())
-                .setWhat(atomName.hashCode()));
-    }
-
-    /**
-     * Adds an atom to a gauge metric of a config
-     *
-     * @param conf        configuration
-     * @param atomId      atom id (from atoms.proto)
-     * @param gaugeMetric the gauge metric to add
-     */
-    protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId,
-            GaugeMetric.Builder gaugeMetric) throws Exception {
-        final String atomName = "Atom" + System.nanoTime();
-        final String gaugeName = "Gauge" + System.nanoTime();
-        final String predicateName = "APP_BREADCRUMB";
-        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
-        conf.addAtomMatcher(AtomMatcher.newBuilder()
-                .setId(atomName.hashCode())
-                .setSimpleAtomMatcher(sam));
-        final String predicateTrueName = "APP_BREADCRUMB_1";
-        final String predicateFalseName = "APP_BREADCRUMB_2";
-        conf.addAtomMatcher(AtomMatcher.newBuilder()
-                .setId(predicateTrueName.hashCode())
-                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                                .setEqInt(1)
-                        )
-                )
-        )
-                // Used to trigger predicate
-                .addAtomMatcher(AtomMatcher.newBuilder()
-                        .setId(predicateFalseName.hashCode())
-                        .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                                        .setEqInt(2)
-                                )
-                        )
-                );
-        conf.addPredicate(Predicate.newBuilder()
-                .setId(predicateName.hashCode())
-                .setSimplePredicate(SimplePredicate.newBuilder()
-                        .setStart(predicateTrueName.hashCode())
-                        .setStop(predicateFalseName.hashCode())
-                        .setCountNesting(false)
-                )
-        );
-        gaugeMetric
-                .setId(gaugeName.hashCode())
-                .setWhat(atomName.hashCode())
-                .setCondition(predicateName.hashCode());
-        conf.addGaugeMetric(gaugeMetric.build());
-    }
-
-    /**
-     * Adds an atom to a gauge metric of a config
-     *
-     * @param conf      configuration
-     * @param atomId    atom id (from atoms.proto)
-     * @param dimension dimension is needed for most pulled atoms
-     */
-    protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId,
-            @Nullable FieldMatcher.Builder dimension) throws Exception {
-        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
-                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
-                .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE)
-                .setMaxNumGaugeAtomsPerBucket(10000)
-                .setBucket(TimeUnit.CTS);
-        if (dimension != null) {
-            gaugeMetric.setDimensionsInWhat(dimension.build());
-        }
-        addGaugeAtom(conf, atomId, gaugeMetric);
-    }
-
-    /**
-     * Asserts that each set of states in stateSets occurs at least once in data.
-     * Asserts that the states in data occur in the same order as the sets in stateSets.
-     *
-     * @param stateSets        A list of set of states, where each set represents an equivalent
-     *                         state of the device for the purpose of CTS.
-     * @param data             list of EventMetricData from statsd, produced by
-     *                         getReportMetricListData()
-     * @param wait             expected duration (in ms) between state changes; asserts that the
-     *                         actual wait
-     *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
-     *                         assertion.
-     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
-     */
-    public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
-            int wait, Function<Atom, Integer> getStateFromAtom) {
-        // Sometimes, there are more events than there are states.
-        // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
-        assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
-        int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
-        for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
-            Atom atom = data.get(dataIndex).getAtom();
-            int state = getStateFromAtom.apply(atom);
-            // If state is in the current state set, we do not assert anything.
-            // If it is not, we expect to have transitioned to the next state set.
-            if (stateSets.get(stateSetIndex).contains(state)) {
-                // No need to assert anything. Just log it.
-                LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
-                        + "in stateSetIndex " + stateSetIndex + ":\n"
-                        + data.get(dataIndex).getAtom().toString());
-            } else {
-                stateSetIndex += 1;
-                LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
-                        + " in stateSetIndex " + stateSetIndex + ":\n"
-                        + data.get(dataIndex).getAtom().toString());
-                assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
-                assertWithMessage("Too many states").that(stateSetIndex)
-                    .isLessThan(stateSets.size());
-                assertWithMessage(String.format("Is in wrong state (%d)", state))
-                    .that(stateSets.get(stateSetIndex)).contains(state);
-                if (wait > 0) {
-                    assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
-                            wait / 2, wait * 5);
-                }
-            }
-        }
-        assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
-    }
-
-    /**
-     * Removes all elements from data prior to the first occurrence of an element of state. After
-     * this method is called, the first element of data (if non-empty) is guaranteed to be an
-     * element in state.
-     *
-     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
-     */
-    public void popUntilFind(List<EventMetricData> data, Set<Integer> state,
-            Function<Atom, Integer> getStateFromAtom) {
-        int firstStateIdx;
-        for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
-            Atom atom = data.get(firstStateIdx).getAtom();
-            if (state.contains(getStateFromAtom.apply(atom))) {
-                break;
-            }
-        }
-        if (firstStateIdx == 0) {
-            // First first element already is in state, so there's nothing to do.
-            return;
-        }
-        data.subList(0, firstStateIdx).clear();
-    }
-
-    /**
-     * Removes all elements from data after to the last occurrence of an element of state. After
-     * this method is called, the last element of data (if non-empty) is guaranteed to be an
-     * element in state.
-     *
-     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
-     */
-    public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state,
-        Function<Atom, Integer> getStateFromAtom) {
-        int lastStateIdx;
-        for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
-            Atom atom = data.get(lastStateIdx).getAtom();
-            if (state.contains(getStateFromAtom.apply(atom))) {
-                break;
-            }
-        }
-        if (lastStateIdx == data.size()-1) {
-            // Last element already is in state, so there's nothing to do.
-            return;
-        }
-        data.subList(lastStateIdx+1, data.size()).clear();
-    }
-
-    /** Returns the UID of the host, which should always either be SHELL (2000). */
-    protected int getHostUid() throws DeviceNotAvailableException {
-        return SHELL_UID;
-    }
-
-    protected String getProperty(String prop) throws Exception {
-        return getDevice().executeShellCommand("getprop " + prop).replace("\n", "");
-    }
-
-    protected void turnScreenOn() throws Exception {
-        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        getDevice().executeShellCommand("wm dismiss-keyguard");
-    }
-
-    protected void turnScreenOff() throws Exception {
-        getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
-    }
-
-    protected void setChargingState(int state) throws Exception {
-        getDevice().executeShellCommand("cmd battery set status " + state);
-    }
-
-    protected void unplugDevice() throws Exception {
-        // On batteryless devices on Android P or above, the 'unplug' command
-        // alone does not simulate the really unplugged state.
-        //
-        // This is because charging state is left as "unknown". Unless a valid
-        // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
-        // framework does not consider the device as running on battery.
-        setChargingState(3);
-
-        getDevice().executeShellCommand("cmd battery unplug");
-    }
-
-    protected void plugInAc() throws Exception {
-        getDevice().executeShellCommand("cmd battery set ac 1");
-    }
-
-    protected void plugInUsb() throws Exception {
-        getDevice().executeShellCommand("cmd battery set usb 1");
-    }
-
-    protected void plugInWireless() throws Exception {
-        getDevice().executeShellCommand("cmd battery set wireless 1");
-    }
-
-    protected void enableLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats enable");
-    }
-
-    protected void resetLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats reset");
-    }
-
-    protected void disableLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats disable");
-    }
-
-    protected void enableBinderStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
-    }
-
-    protected void resetBinderStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
-    }
-
-    protected void disableBinderStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
-    }
-
-    protected void binderStatsNoSampling() throws Exception {
-        getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
-    }
-
-    protected void setUpLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats enable");
-        getDevice().executeShellCommand("cmd looper_stats sampling_interval 1");
-        getDevice().executeShellCommand("cmd looper_stats reset");
-    }
-
-    protected void cleanUpLooperStats() throws Exception {
-        getDevice().executeShellCommand("cmd looper_stats disable");
-    }
-
-    public void setAppBreadcrumbPredicate() throws Exception {
-        doAppBreadcrumbReportedStart(1);
-    }
-
-    public void clearAppBreadcrumbPredicate() throws Exception {
-        doAppBreadcrumbReportedStart(2);
-    }
-
-    public void doAppBreadcrumbReportedStart(int label) throws Exception {
-        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal());
-    }
-
-    public void doAppBreadcrumbReportedStop(int label) throws Exception {
-        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal());
-    }
-
-    public void doAppBreadcrumbReported(int label) throws Exception {
-        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-    }
-
-    public void doAppBreadcrumbReported(int label, int state) throws Exception {
-        getDevice().executeShellCommand(String.format(
-                "cmd stats log-app-breadcrumb %d %d %d", SHELL_UID, label, state));
-    }
-
-    protected void setBatteryLevel(int level) throws Exception {
-        getDevice().executeShellCommand("cmd battery set level " + level);
-    }
-
-    protected void resetBatteryStatus() throws Exception {
-        getDevice().executeShellCommand("cmd battery reset");
-    }
-
-    protected int getScreenBrightness() throws Exception {
-        return Integer.parseInt(
-                getDevice().executeShellCommand("settings get system screen_brightness").trim());
-    }
-
-    protected void setScreenBrightness(int brightness) throws Exception {
-        getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
-    }
-
-    // Gets whether "Always on Display" setting is enabled.
-    // In rare cases, this is different from whether the device can enter SCREEN_STATE_DOZE.
-    protected String getAodState() throws Exception {
-        return getDevice().executeShellCommand("settings get secure doze_always_on");
-    }
-
-    protected void setAodState(String state) throws Exception {
-        getDevice().executeShellCommand("settings put secure doze_always_on " + state);
-    }
-
-    protected boolean isScreenBrightnessModeManual() throws Exception {
-        String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode");
-        return Integer.parseInt(mode.trim()) == 0;
-    }
-
-    protected void setScreenBrightnessMode(boolean manual) throws Exception {
-        getDevice().executeShellCommand(
-                "settings put system screen_brightness_mode " + (manual ? 0 : 1));
-    }
-
-    protected void enterDozeModeLight() throws Exception {
-        getDevice().executeShellCommand("dumpsys deviceidle force-idle light");
-    }
-
-    protected void enterDozeModeDeep() throws Exception {
-        getDevice().executeShellCommand("dumpsys deviceidle force-idle deep");
-    }
-
-    protected void leaveDozeMode() throws Exception {
-        getDevice().executeShellCommand("dumpsys deviceidle unforce");
-        getDevice().executeShellCommand("dumpsys deviceidle disable");
-        getDevice().executeShellCommand("dumpsys deviceidle enable");
-    }
-
-    protected void turnBatterySaverOn() throws Exception {
-        unplugDevice();
-        getDevice().executeShellCommand("settings put global low_power 1");
-    }
-
-    protected void turnBatterySaverOff() throws Exception {
-        getDevice().executeShellCommand("settings put global low_power 0");
-        getDevice().executeShellCommand("cmd battery reset");
-    }
-
-    protected void turnBatteryStatsAutoResetOn() throws Exception {
-        getDevice().executeShellCommand("dumpsys batterystats enable no-auto-reset");
-    }
-
-    protected void turnBatteryStatsAutoResetOff() throws Exception {
-        getDevice().executeShellCommand("dumpsys batterystats enable no-auto-reset");
-    }
-
-    protected void flushBatteryStatsHandlers() throws Exception {
-        // Dumping batterystats will flush everything in the batterystats handler threads.
-        getDevice().executeShellCommand(DUMP_BATTERYSTATS_CMD);
-    }
-
-    protected void rebootDevice() throws Exception {
-        getDevice().rebootUntilOnline();
-    }
-
-    /**
-     * Asserts that the two events are within the specified range of each other.
-     *
-     * @param d0        the event that should occur first
-     * @param d1        the event that should occur second
-     * @param minDiffMs d0 should precede d1 by at least this amount
-     * @param maxDiffMs d0 should precede d1 by at most this amount
-     */
-    public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
-            int minDiffMs, int maxDiffMs) {
-        long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
-        assertWithMessage("Illegal time difference")
-            .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
-    }
-
-    protected String getCurrentLogcatDate() throws Exception {
-        // TODO: Do something more robust than this for getting logcat markers.
-        long timestampMs = getDevice().getDeviceDate();
-        return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
-                .format(new Date(timestampMs));
-    }
-
-    protected String getLogcatSince(String date, String logcatParams) throws Exception {
-        return getDevice().executeShellCommand(String.format(
-                "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
-    }
-
-    // TODO: Remove this and migrate all usages to createConfigBuilder()
-    protected StatsdConfig.Builder getPulledConfig() {
-        return createConfigBuilder();
-    }
-    /**
-     * Determines if the device has the given feature.
-     * Prints a warning if its value differs from requiredAnswer.
-     */
-    protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
-        final String features = getDevice().executeShellCommand("pm list features");
-        StringTokenizer featureToken = new StringTokenizer(features, "\n");
-        boolean hasIt = false;
-
-        while (featureToken.hasMoreTokens()) {
-            if (("feature:" + featureName).equals(featureToken.nextToken())) {
-                 hasIt = true;
-                 break;
-            }
-        }
-
-        if (hasIt != requiredAnswer) {
-            LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
-                    + featureName);
-        }
-        return hasIt == requiredAnswer;
-    }
-
-    /**
-     * Determines if the device has |file|.
-     */
-    protected boolean doesFileExist(String file) throws Exception {
-        return getDevice().doesFileExist(file);
-    }
-
-    protected void turnOnAirplaneMode() throws Exception {
-        getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
-    }
-
-    protected void turnOffAirplaneMode() throws Exception {
-        getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
-    }
-
-    /**
-     * Returns a list of fields and values for {@code className} from {@link TelephonyDebugService}
-     * output.
-     *
-     * <p>Telephony dumpsys output does not support proto at the moment. This method provides
-     * limited support for parsing its output. Specifically, it does not support arrays or
-     * multi-line values.
-     */
-    private List<Map<String, String>> getTelephonyDumpEntries(String className) throws Exception {
-        // Matches any line with indentation, except for lines with only spaces
-        Pattern indentPattern = Pattern.compile("^(\\s*)[^ ].*$");
-        // Matches pattern for class, e.g. "    Phone:"
-        Pattern classNamePattern = Pattern.compile("^(\\s*)" + Pattern.quote(className) + ":.*$");
-        // Matches pattern for key-value pairs, e.g. "     mPhoneId=1"
-        Pattern keyValuePattern = Pattern.compile("^(\\s*)([a-zA-Z]+[a-zA-Z0-9_]*)\\=(.+)$");
-        String response =
-                getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
-        Queue<String> responseLines = new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
-
-        List<Map<String, String>> results = new ArrayList<>();
-        while (responseLines.peek() != null) {
-            Matcher matcher = classNamePattern.matcher(responseLines.poll());
-            if (matcher.matches()) {
-                final int classIndentLevel = matcher.group(1).length();
-                final Map<String, String> instanceEntries = new HashMap<>();
-                while (responseLines.peek() != null) {
-                    // Skip blank lines
-                    matcher = indentPattern.matcher(responseLines.peek());
-                    if (responseLines.peek().length() == 0 || !matcher.matches()) {
-                        responseLines.poll();
-                        continue;
-                    }
-                    // Finish (without consuming the line) if already parsed past this instance
-                    final int indentLevel = matcher.group(1).length();
-                    if (indentLevel <= classIndentLevel) {
-                        break;
-                    }
-                    // Parse key-value pair if it belongs to the instance directly
-                    matcher = keyValuePattern.matcher(responseLines.poll());
-                    if (indentLevel == classIndentLevel + 1 && matcher.matches()) {
-                        instanceEntries.put(matcher.group(2), matcher.group(3));
-                    }
-                }
-                results.add(instanceEntries);
-            }
-        }
-        return results;
-    }
-
-    protected int getActiveSimSlotCount() throws Exception {
-        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
-        long count = slots.stream().filter(slot -> "true".equals(slot.get("mActive"))).count();
-        return Math.toIntExact(count);
-    }
-
-    /**
-     * Returns the upper bound of active SIM profile count.
-     *
-     * <p>The value is an upper bound as eSIMs without profiles are also counted in.
-     */
-    protected int getActiveSimCountUpperBound() throws Exception {
-        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
-        long count = slots.stream().filter(slot ->
-                "true".equals(slot.get("mActive"))
-                && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))).count();
-        return Math.toIntExact(count);
-    }
-
-    /**
-     * Returns the upper bound of active eSIM profile count.
-     *
-     * <p>The value is an upper bound as eSIMs without profiles are also counted in.
-     */
-    protected int getActiveEsimCountUpperBound() throws Exception {
-        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
-        long count = slots.stream().filter(slot ->
-                "true".equals(slot.get("mActive"))
-                && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))
-                && "true".equals(slot.get("mIsEuicc"))).count();
-        return Math.toIntExact(count);
-    }
-
-    protected boolean hasGsmPhone() throws Exception {
-        // Not using log entries or ServiceState in the dump since they may or may not be present,
-        // which can make the test flaky
-        return getTelephonyDumpEntries("Phone").stream()
-                .anyMatch(phone ->
-                        String.format("%d", PHONE_TYPE_GSM).equals(phone.get("getPhoneType()")));
-    }
-
-    protected boolean hasCdmaPhone() throws Exception {
-        // Not using log entries or ServiceState in the dump due to the same reason as hasGsmPhone()
-        return getTelephonyDumpEntries("Phone").stream()
-                .anyMatch(phone ->
-                        String.format("%d", PHONE_TYPE_CDMA).equals(phone.get("getPhoneType()"))
-                        || String.format("%d", PHONE_TYPE_CDMA_LTE)
-                                .equals(phone.get("getPhoneType()")));
-    }
-
-    // Checks that a timestamp has been truncated to be a multiple of 5 min
-    protected void assertTimestampIsTruncated(long timestampNs) {
-        long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
-        assertWithMessage("Timestamp is not truncated")
-                .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
-    }
-
-    protected List<EventMetricData> backfillAggregatedAtomsInEventMetric(
-            EventMetricData metricData) {
-      if (!metricData.hasAggregatedAtomInfo()) {
-        return Collections.singletonList(metricData);
-      }
-      List<EventMetricData> data = new ArrayList<>();
-      StatsLog.AggregatedAtomInfo atomInfo = metricData.getAggregatedAtomInfo();
-      for (long timestamp : atomInfo.getElapsedTimestampNanosList()) {
-        data.add(EventMetricData.newBuilder()
-                     .setAtom(atomInfo.getAtom())
-                     .setElapsedTimestampNanos(timestamp)
-                     .build());
-      }
-      return data;
-    }
-}
diff --git a/tests/src/android/cts/statsd/atom/BaseTestCase.java b/tests/src/android/cts/statsd/atom/BaseTestCase.java
deleted file mode 100644
index 0c9921e..0000000
--- a/tests/src/android/cts/statsd/atom/BaseTestCase.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.cts.statsd.atom;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.cts.statsd.validation.ValidationTestUtil;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.CollectingByteOutputReceiver;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.TestDescription;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestRunResult;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
-import com.google.protobuf.Parser;
-
-import java.io.FileNotFoundException;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-// Largely copied from incident's ProtoDumpTestCase
-public class BaseTestCase extends DeviceTestCase implements IBuildReceiver {
-
-    protected IBuildInfo mCtsBuild;
-
-    private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        assertThat(mCtsBuild).isNotNull();
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        mCtsBuild = buildInfo;
-    }
-
-    public IBuildInfo getBuild() {
-        return mCtsBuild;
-    }
-
-    /**
-     * Create and return {@link ValidationTestUtil} and give it the current build.
-     */
-    public ValidationTestUtil createValidationUtil() {
-        ValidationTestUtil util = new ValidationTestUtil();
-        util.setBuild(getBuild());
-        return util;
-    }
-
-    /**
-     * Call onto the device with an adb shell command and get the results of
-     * that as a proto of the given type.
-     *
-     * @param parser A protobuf parser object. e.g. MyProto.parser()
-     * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
-     *
-     * @throws DeviceNotAvailableException If there was a problem communicating with
-     *      the test device.
-     * @throws InvalidProtocolBufferException If there was an error parsing
-     *      the proto. Note that a 0 length buffer is not necessarily an error.
-     */
-    public <T extends MessageLite> T getDump(Parser<T> parser, String command)
-            throws DeviceNotAvailableException, InvalidProtocolBufferException {
-        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
-        getDevice().executeShellCommand(command, receiver);
-        if (false) {
-            CLog.d("Command output while parsing " + parser.getClass().getCanonicalName()
-                    + " for command: " + command + "\n"
-                    + BufferDebug.debugString(receiver.getOutput(), -1));
-        }
-        try {
-            return parser.parseFrom(receiver.getOutput());
-        } catch (Exception ex) {
-            CLog.d("Error parsing " + parser.getClass().getCanonicalName() + " for command: "
-                    + command
-                    + BufferDebug.debugString(receiver.getOutput(), 16384));
-            throw ex;
-        }
-    }
-
-    /**
-     * Install a device side test package.
-     *
-     * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
-     * @param grantPermissions whether to give runtime permissions.
-     */
-    protected void installPackage(String appFileName, boolean grantPermissions)
-            throws FileNotFoundException, DeviceNotAvailableException {
-        CLog.d("Installing app " + appFileName);
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        final String result = getDevice().installPackage(
-                buildHelper.getTestFile(appFileName), true, grantPermissions);
-        assertWithMessage(String.format("Failed to install %s: %s", appFileName, result))
-            .that(result).isNull();
-    }
-
-    protected CompatibilityBuildHelper getBuildHelper() {
-        return new CompatibilityBuildHelper(mCtsBuild);
-    }
-
-    /**
-     * Run a device side test.
-     *
-     * @param pkgName Test package name, such as "com.android.server.cts.netstats".
-     * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
-     * @param testMethodName Test method name.
-     * @return {@link TestRunResult} of this invocation.
-     * @throws DeviceNotAvailableException
-     */
-    @Nonnull
-    protected TestRunResult runDeviceTests(@Nonnull String pkgName,
-            @Nullable String testClassName, @Nullable String testMethodName)
-            throws DeviceNotAvailableException {
-        if (testClassName != null && testClassName.startsWith(".")) {
-            testClassName = pkgName + testClassName;
-        }
-
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
-                pkgName, TEST_RUNNER, getDevice().getIDevice());
-        if (testClassName != null && testMethodName != null) {
-            testRunner.setMethodName(testClassName, testMethodName);
-        } else if (testClassName != null) {
-            testRunner.setClassName(testClassName);
-        }
-
-        CollectingTestListener listener = new CollectingTestListener();
-        assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
-
-        final TestRunResult result = listener.getCurrentRunResults();
-        if (result.isRunFailure()) {
-            throw new Error("Failed to successfully run device tests for "
-                    + result.getName() + ": " + result.getRunFailureMessage());
-        }
-        if (result.getNumTests() == 0) {
-            throw new Error("No tests were run on the device");
-        }
-
-        if (result.hasFailedTests()) {
-            // build a meaningful error message
-            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
-            for (Map.Entry<TestDescription, TestResult> resultEntry :
-                    result.getTestResults().entrySet()) {
-                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
-                    errorBuilder.append(resultEntry.getKey().toString());
-                    errorBuilder.append(":\n");
-                    errorBuilder.append(resultEntry.getValue().getStackTrace());
-                }
-            }
-            throw new AssertionError(errorBuilder.toString());
-        }
-
-        return result;
-    }
-}
diff --git a/tests/src/android/cts/statsd/atom/DeviceAtomTestCase.java b/tests/src/android/cts/statsd/atom/DeviceAtomTestCase.java
deleted file mode 100644
index d641ebc..0000000
--- a/tests/src/android/cts/statsd/atom/DeviceAtomTestCase.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.cts.statsd.atom;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.MessageMatcher;
-import com.android.internal.os.StatsdConfigProto.Position;
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.os.StatsLog.EventMetricData;
-import com.android.tradefed.log.LogUtil;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
- */
-public class DeviceAtomTestCase extends AtomTestCase {
-
-    public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
-    public static final String DEVICE_SIDE_TEST_PACKAGE =
-            "com.android.server.cts.device.statsd";
-    public static final long DEVICE_SIDE_TEST_PACKAGE_VERSION = 10;
-    public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME =
-            "com.android.server.cts.device.statsd.StatsdCtsForegroundService";
-    private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT =
-            "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
-    public static final long DEVICE_SIDE_TEST_PKG_HASH =
-            Long.parseUnsignedLong("15694052924544098582");
-
-    // Constants from device side tests (not directly accessible here).
-    public static final String KEY_ACTION = "action";
-    public static final String ACTION_LMK = "action.lmk";
-
-    public static final String CONFIG_NAME = "cts_config";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        installTestApp();
-        Thread.sleep(1000);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        super.tearDown();
-    }
-
-    /**
-     * Performs a device-side test by calling a method on the app and returns its stats events.
-     * @param methodName the name of the method in the app's AtomTests to perform
-     * @param atom atom tag (from atoms.proto)
-     * @param key atom's field corresponding to state
-     * @param stateOn 'on' value
-     * @param stateOff 'off' value
-     * @param minTimeDiffMs max allowed time between start and stop
-     * @param maxTimeDiffMs min allowed time between start and stop
-     * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
-     * @return list of events with the app's uid matching the configuration defined by the params.
-     */
-    protected List<EventMetricData> doDeviceMethodOnOff(
-            String methodName, int atom, int key, int stateOn, int stateOff,
-            int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn));
-        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff));
-        List<EventMetricData> data = doDeviceMethod(methodName, conf);
-
-        if (demandExactlyTwo) {
-            assertThat(data).hasSize(2);
-        } else {
-            assertThat(data.size()).isAtLeast(2);
-        }
-        assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
-        return data;
-    }
-
-    /**
-     *
-     * @param methodName the name of the method in the app's AtomTests to perform
-     * @param cfg statsd configuration
-     * @return list of events with the app's uid matching the configuration.
-     */
-    protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
-            throws Exception {
-        removeConfig(CONFIG_ID);
-        getReportList();  // Clears previous data on disk.
-        uploadConfig(cfg);
-        int appUid = getUid();
-        LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
-
-        return getEventMetricDataList();
-    }
-
-    protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception {
-        StatsdConfig.Builder conf = createConfigBuilder();
-        addAtomEvent(conf, atomTag, useAttribution);
-        uploadConfig(conf);
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the given key AND has the app's uid.
-     * @param conf configuration
-     * @param atomTag atom tag (from atoms.proto)
-     * @param fvm FieldValueMatcher.Builder for the relevant key
-     */
-    @Override
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)
-            throws Exception {
-
-        final int UID_KEY = 1;
-        FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY);
-        addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid));
-    }
-
-    /**
-     * Adds an event to the config for an atom that matches the app's uid.
-     * @param conf configuration
-     * @param atomTag atom tag (from atoms.proto)
-     * @param useAttribution If true, the atom has a uid within an attribution node. Else, the atom
-     * has a uid but not in an attribution node.
-     */
-    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
-            boolean useAttribution) throws Exception {
-        final int UID_KEY = 1;
-        FieldValueMatcher.Builder fvmUid;
-        if (useAttribution) {
-            fvmUid = createAttributionFvm(UID_KEY);
-        } else {
-            fvmUid = createFvm(UID_KEY).setEqString(DEVICE_SIDE_TEST_PACKAGE);
-        }
-        addAtomEvent(conf, atomTag, Arrays.asList(fvmUid));
-    }
-
-    /**
-     * Creates a FieldValueMatcher for atoms that use AttributionNode
-     */
-    protected FieldValueMatcher.Builder createAttributionFvm(int field) {
-        final int ATTRIBUTION_NODE_UID_KEY = 1;
-        return createFvm(field).setPosition(Position.ANY)
-                .setMatchesTuple(MessageMatcher.newBuilder()
-                        .addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY)
-                                .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
-    }
-
-    /**
-     * Gets the uid of the test app.
-     */
-    protected int getUid() throws Exception {
-        int currentUser = getDevice().getCurrentUser();
-        final String packages = getDevice().executeShellCommand("cmd package list packages -U"
-                + " --user " + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
-
-        // Split package list by lines
-        // Sample packages response:
-        // package:com.android.server.cts.device.statsd.host uid:1010033
-        // package:com.android.server.cts.device.statsd uid:1010034
-        final String[] lines = packages.split("[\\r\\n]+");
-        for (final String line : lines) {
-            if (line.startsWith("package:" + DEVICE_SIDE_TEST_PACKAGE + " ")) {
-                final int uidIndex = line.lastIndexOf(":") + 1;
-                final int uid = Integer.parseInt(line.substring(uidIndex).trim());
-                assertThat(uid).isGreaterThan(10_000);
-                return uid;
-            }
-        }
-        throw new Error(
-                String.format("Could not find installed package: %s", DEVICE_SIDE_TEST_PACKAGE));
-    }
-
-    /**
-     * Installs the test apk.
-     */
-    protected void installTestApp() throws Exception {
-        installPackage(DEVICE_SIDE_TEST_APK, true);
-        LogUtil.CLog.i("Installing device-side test app with uid " + getUid());
-        allowBackgroundServices();
-    }
-
-    /**
-     * Uninstalls the test apk.
-     */
-    protected void uninstallPackage() throws Exception{
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-    }
-
-    /**
-     * Required to successfully start a background service from adb in Android O.
-     */
-    protected void allowBackgroundServices() throws Exception {
-        getDevice().executeShellCommand(String.format(
-                "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
-    }
-
-    /**
-     * Runs a (background) service to perform the given action.
-     * @param actionValue the action code constants indicating the desired action to perform.
-     */
-    protected void executeBackgroundService(String actionValue) throws Exception {
-        allowBackgroundServices();
-        getDevice().executeShellCommand(String.format(
-                "am startservice -n '%s' -e %s %s",
-                DEVICE_SIDE_BG_SERVICE_COMPONENT,
-                KEY_ACTION, actionValue));
-    }
-
-
-    /** Make the test app standby-active so it can run syncs and jobs immediately. */
-    protected void allowImmediateSyncs() throws Exception {
-        getDevice().executeShellCommand("am set-standby-bucket "
-                + DEVICE_SIDE_TEST_PACKAGE + " active");
-    }
-
-    /**
-     * Runs the specified activity.
-     */
-    protected void runActivity(String activity, String actionKey, String actionValue)
-            throws Exception {
-        runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG);
-    }
-
-    /**
-     * Runs the specified activity.
-     */
-    protected void runActivity(String activity, String actionKey, String actionValue,
-            long waitTime) throws Exception {
-        try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) {
-            Thread.sleep(waitTime);
-        }
-    }
-
-    /**
-     * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
-     * when closed.
-     *
-     * <p>Example usage:
-     * <pre>
-     *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
-     *         doStuff();
-     *     }
-     * </pre>
-     */
-    protected AutoCloseable withActivity(String activity, String actionKey, String actionValue)
-            throws Exception {
-        String intentString = null;
-        if (actionKey != null && actionValue != null) {
-            intentString = actionKey + " " + actionValue;
-        }
-        if (intentString == null) {
-            getDevice().executeShellCommand(
-                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity);
-        } else {
-            getDevice().executeShellCommand(
-                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " +
-                            intentString);
-        }
-        return () -> {
-            getDevice().executeShellCommand(
-                    "am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
-            Thread.sleep(WAIT_TIME_SHORT);
-        };
-    }
-
-    protected void resetBatteryStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys batterystats --reset");
-    }
-
-    protected void clearProcStats() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --clear");
-    }
-
-    protected void startProcStatsTesting() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --start-testing");
-    }
-
-    protected void stopProcStatsTesting() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --stop-testing");
-    }
-
-    protected void commitProcStatsToDisk() throws Exception {
-        getDevice().executeShellCommand("dumpsys procstats --commit");
-    }
-
-    protected void rebootDeviceAndWaitUntilReady() throws Exception {
-        rebootDevice();
-        // Wait for 5 mins.
-        assertWithMessage("Device failed to boot")
-            .that(getDevice().waitForBootComplete(300_000)).isTrue();
-        assertWithMessage("Stats service failed to start")
-            .that(waitForStatsServiceStart(60_000)).isTrue();
-        Thread.sleep(2_000);
-    }
-
-    protected boolean waitForStatsServiceStart(final long waitTime) throws Exception {
-        LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime);
-        int counter = 1;
-        long startTime = System.currentTimeMillis();
-        while ((System.currentTimeMillis() - startTime) < waitTime) {
-            if ("running".equals(getProperty("init.svc.statsd"))) {
-                return true;
-            }
-            Thread.sleep(Math.min(200 * counter, 2_000));
-            counter++;
-        }
-        LogUtil.CLog.w("Stats service did not start after %d ms", waitTime);
-        return false;
-    }
-
-    boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
-        final String output = getDevice().executeShellCommand(
-                "settings get global netstats_combine_subtype_enabled").trim();
-        return output.equals("1");
-    }
-
-    void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
-        getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
-                + (enable ? "1" : "0"));
-    }
-}
diff --git a/tests/src/android/cts/statsd/atom/ProcStateTestCase.java b/tests/src/android/cts/statsd/atom/ProcStateTestCase.java
index 2fa4233..09c508b 100644
--- a/tests/src/android/cts/statsd/atom/ProcStateTestCase.java
+++ b/tests/src/android/cts/statsd/atom/ProcStateTestCase.java
@@ -15,10 +15,20 @@
  */
 package android.cts.statsd.atom;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.os.AtomsProto.Atom;
 import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
 import java.util.Arrays;
 import java.util.HashSet;
@@ -31,46 +41,74 @@
 /**
  * Base class for manipulating process states
  */
-public class ProcStateTestCase extends DeviceAtomTestCase {
+public class ProcStateTestCase extends DeviceTestCase implements IBuildReceiver {
 
-  private static final String TAG = "Statsd.ProcStateTestCase";
+    private static final String TAG = "Statsd.ProcStateTestCase";
 
-  private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
-          = "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity";
-  private static final String DEVICE_SIDE_FG_SERVICE_COMPONENT
-          = "com.android.server.cts.device.statsd/.StatsdCtsForegroundService";
+    private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
+            = "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity";
+    private static final String DEVICE_SIDE_FG_SERVICE_COMPONENT
+            = "com.android.server.cts.device.statsd/.StatsdCtsForegroundService";
 
-  // Constants from the device-side tests (not directly accessible here).
-  public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
-  public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
-  public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
-  public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
-  public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+    private static final String KEY_ACTION = "action";
 
-  // Sleep times (ms) that actions invoke device-side.
-  public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
-  public static final int SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP = 60_000;
-  public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
-  public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
+    // Constants from the device-side tests (not directly accessible here).
+    public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
+    public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
+    public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
+    public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
+    public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
 
+    // Sleep times (ms) that actions invoke device-side.
+    public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
+    public static final int SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP = 60_000;
+    public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
+    public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
 
-  /**
-   * Runs an activity (in the foreground) to perform the given action.
-   * @param actionValue the action code constants indicating the desired action to perform.
-   */
-  protected void executeForegroundActivity(String actionValue) throws Exception {
-    getDevice().executeShellCommand(String.format(
-            "am start -n '%s' -e %s %s",
-            DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
-            KEY_ACTION, actionValue));
-  }
+    protected IBuildInfo mCtsBuild;
 
-  /**
-   * Runs a simple foreground service.
-   */
-  protected void executeForegroundService() throws Exception {
-    executeForegroundActivity(ACTION_END_IMMEDIATELY);
-    getDevice().executeShellCommand(String.format(
-            "am startservice -n '%s'", DEVICE_SIDE_FG_SERVICE_COMPONENT));
-  }
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    /**
+     * Runs an activity (in the foreground) to perform the given action.
+     *
+     * @param actionValue the action code constants indicating the desired action to perform.
+     */
+    protected void executeForegroundActivity(String actionValue) throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "am start -n '%s' -e %s %s",
+                DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
+                KEY_ACTION, actionValue));
+    }
+
+    /**
+     * Runs a simple foreground service.
+     */
+    protected void executeForegroundService() throws Exception {
+        executeForegroundActivity(ACTION_END_IMMEDIATELY);
+        getDevice().executeShellCommand(String.format(
+                "am startservice -n '%s'", DEVICE_SIDE_FG_SERVICE_COMPONENT));
+    }
 }
diff --git a/tests/src/android/cts/statsd/metadata/MetadataTestCase.java b/tests/src/android/cts/statsd/metadata/MetadataTestCase.java
index 0ccb13c..1f0e5a1 100644
--- a/tests/src/android/cts/statsd/metadata/MetadataTestCase.java
+++ b/tests/src/android/cts/statsd/metadata/MetadataTestCase.java
@@ -16,19 +16,37 @@
 
 package android.cts.statsd.metadata;
 
-import android.cts.statsd.atom.AtomTestCase;
-import com.android.internal.os.StatsdConfigProto;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsd.atom.BufferDebug;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.StatsLog.StatsdStatsReport;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
-public class MetadataTestCase extends AtomTestCase {
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+public class MetadataTestCase extends DeviceTestCase implements IBuildReceiver {
     public static final String DUMP_METADATA_CMD = "cmd stats print-stats";
 
+    protected IBuildInfo mCtsBuild;
+
     protected StatsdStatsReport getStatsdStatsReport() throws Exception {
         try {
-            StatsdStatsReport report = getDump(StatsdStatsReport.parser(),
+            StatsdStatsReport report = MetricsUtils.getDump(getDevice(), StatsdStatsReport.parser(),
                     String.join(" ", DUMP_METADATA_CMD, "--proto"));
             return report;
         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
@@ -38,8 +56,33 @@
     }
 
     protected final StatsdConfig.Builder getBaseConfig() throws Exception {
-      StatsdConfig.Builder builder = createConfigBuilder();
-      addAtomEvent(builder, Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
-      return builder;
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        ConfigUtils.addEventMetric(builder, Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
+        return builder;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
     }
 }
diff --git a/tests/src/android/cts/statsd/metadata/MetadataTests.java b/tests/src/android/cts/statsd/metadata/MetadataTests.java
index 5c59b61..1f498f9 100644
--- a/tests/src/android/cts/statsd/metadata/MetadataTests.java
+++ b/tests/src/android/cts/statsd/metadata/MetadataTests.java
@@ -15,25 +15,27 @@
  */
 package android.cts.statsd.metadata;
 
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.cts.statsd.atom.AtomTestCase;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
 
-import com.android.internal.os.StatsdConfigProto;
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
-import com.android.internal.os.StatsdConfigProto.Subscription;
-import com.android.internal.os.StatsdConfigProto.TimeUnit;
-import com.android.internal.os.StatsdConfigProto.ValueMetric;
-import com.android.os.AtomsProto.AnomalyDetected;
-import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto;
 import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.EventMetricData;
 import com.android.os.StatsLog.StatsdStatsReport;
 import com.android.os.StatsLog.StatsdStatsReport.ConfigStats;
+import com.android.os.StatsLog.StatsdStatsReport.LogLossStats;
+import com.android.os.StatsLog.StatsdStatsReport.SocketLossStats.LossStatsPerUid;
+import com.android.os.StatsLog.StatsdStatsReport.SocketLossStats.LossStatsPerUid.AtomIdLossStats;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.util.RunUtil;
 
-
-import java.util.List;
+import java.util.HashSet;
 
 /**
  * Statsd Metadata tests.
@@ -42,24 +44,28 @@
 
     private static final String TAG = "Statsd.MetadataTests";
 
+    private static final int SHELL_UID = 2000;
+
     // Tests that the statsd config is reset after the specified ttl.
     public void testConfigTtl() throws Exception {
         final int TTL_TIME_SEC = 8;
         StatsdConfig.Builder config = getBaseConfig();
         config.setTtlInSeconds(TTL_TIME_SEC); // should reset in this many seconds.
 
-        uploadConfig(config);
+        ConfigUtils.uploadConfig(getDevice(), config);
         long startTime = System.currentTimeMillis();
-        Thread.sleep(WAIT_TIME_SHORT);
-        doAppBreadcrumbReportedStart(/* irrelevant val */ 6); // Event, within < TTL_TIME_SEC secs.
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AtomsProto.AppBreadcrumbReported.State.START.getNumber(), /* irrelevant val */
+                6); // Event, within < TTL_TIME_SEC secs.
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
         StatsdStatsReport report = getStatsdStatsReport(); // Has only been 1 second
         LogUtil.CLog.d("got following statsdstats report: " + report.toString());
         boolean foundActiveConfig = false;
         int creationTime = 0;
-        for (ConfigStats stats: report.getConfigStatsList()) {
-            if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
-                if(!stats.hasDeletionTimeSec()) {
+        for (ConfigStats stats : report.getConfigStatsList()) {
+            if (stats.getId() == ConfigUtils.CONFIG_ID && stats.getUid() == SHELL_UID) {
+                if (!stats.hasDeletionTimeSec()) {
                     assertWithMessage("Found multiple active CTS configs!")
                             .that(foundActiveConfig).isFalse();
                     foundActiveConfig = true;
@@ -69,17 +75,19 @@
         }
         assertWithMessage("Did not find an active CTS config").that(foundActiveConfig).isTrue();
 
-        while(System.currentTimeMillis() - startTime < 8_000) {
-            Thread.sleep(10);
+        while (System.currentTimeMillis() - startTime < 8_000) {
+            RunUtil.getDefault().sleep(10);
         }
-        doAppBreadcrumbReportedStart(/* irrelevant val */ 6); // Event, after TTL_TIME_SEC secs.
-        Thread.sleep(WAIT_TIME_LONG);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AtomsProto.AppBreadcrumbReported.State.START.getNumber(), /* irrelevant val */
+                6); // Event, after TTL_TIME_SEC secs.
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
         report = getStatsdStatsReport();
         LogUtil.CLog.d("got following statsdstats report: " + report.toString());
         foundActiveConfig = false;
         int expectedTime = creationTime + TTL_TIME_SEC;
-        for (ConfigStats stats: report.getConfigStatsList()) {
-            if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
+        for (ConfigStats stats : report.getConfigStatsList()) {
+            if (stats.getId() == ConfigUtils.CONFIG_ID && stats.getUid() == SHELL_UID) {
                 // Original config should be TTL'd
                 if (stats.getCreationTimeSec() == creationTime) {
                     assertWithMessage("Config should have TTL'd but is still active")
@@ -89,7 +97,7 @@
                     ).that(Math.abs(stats.getDeletionTimeSec() - expectedTime)).isAtMost(2);
                 }
                 // There should still be one active config, that is marked as reset.
-                if(!stats.hasDeletionTimeSec()) {
+                if (!stats.hasDeletionTimeSec()) {
                     assertWithMessage("Found multiple active CTS configs!")
                             .that(foundActiveConfig).isFalse();
                     foundActiveConfig = true;
@@ -107,4 +115,126 @@
         assertWithMessage("Did not find an active CTS config after the TTL")
                 .that(foundActiveConfig).isTrue();
     }
+
+    private static final int LIB_STATS_SOCKET_QUEUE_OVERFLOW_ERROR_CODE = 1;
+    private static final int EVENT_STORM_ITERATIONS_COUNT = 10;
+
+    /** Tests that logging many atoms back to back leads to socket overflow and data loss. */
+    public void testAtomLossInfoCollection() throws Exception {
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                ".StatsdStressLogging", "testLogAtomsBackToBack");
+
+        StatsdStatsReport report = getStatsdStatsReport();
+        assertThat(report).isNotNull();
+        boolean detectedLossEventForAppBreadcrumbAtom = false;
+        for (LogLossStats lossStats : report.getDetectedLogLossList()) {
+            if (lossStats.getLastTag() == Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) {
+                detectedLossEventForAppBreadcrumbAtom = true;
+            }
+        }
+
+        assertThat(detectedLossEventForAppBreadcrumbAtom).isTrue();
+    }
+
+    /** Tests that SystemServer logged atoms in case of loss event has error code 1. */
+    public void testSystemServerLossErrorCode() throws Exception {
+        // Starting from VanillaIceCream libstatssocket uses worker thread & dedicated logging queue
+        // to handle atoms for system server (logged with UID 1000)
+        // this test might fail for previous versions due to loss stats last error code check
+        // will not pass
+
+        // Due to info about system server atom loss could be overwritten by APP_BREADCRUMB_REPORTED
+        // loss info run several iterations of this test
+        for (int i = 0; i < EVENT_STORM_ITERATIONS_COUNT; i++) {
+            LogUtil.CLog.d("testSystemServerLossErrorCode iteration #" + i);
+            // logging back to back many atoms to force socket overflow
+            DeviceUtils.runDeviceTests(
+                    getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, ".StatsdStressLogging",
+                    "testLogAtomsBackToBack");
+
+            // Delay to allow statsd socket recover after overflow
+            RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+            // There is some un-deterministic component in AtomLossStats propagation:
+            // - the dumpAtomsLossStats() from the libstatssocket happens ONLY after the
+            //   next successful atom write to socket.
+            // - to avoid socket flood there is also cooldown timer incorporated. If no new atoms -
+            //   loss info will not be propagated, which is intention by design.
+            // Log atoms into socket successfully to trigger libstatsocket dumpAtomsLossStats()
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AtomsProto.AppBreadcrumbReported.State.START.getNumber(), /* irrelevant val */
+                    6); // Event, after TTL_TIME_SEC secs.
+
+            // Delay to allow libstatssocket loss info to be propagated to statsdstats
+            RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+            StatsdStatsReport report = getStatsdStatsReport();
+            assertThat(report).isNotNull();
+            boolean detectedLossEventForAppBreadcrumbAtom = false;
+            boolean detectedLossEventForSystemServer = false;
+            for (LogLossStats lossStats : report.getDetectedLogLossList()) {
+                if (lossStats.getLastTag() == Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) {
+                    detectedLossEventForAppBreadcrumbAtom = true;
+                }
+
+                // it should not happen due to atoms from system servers logged via queue
+                // which should be sufficient to hold them for some time to overcome the
+                // socket overflow time frame
+                if (lossStats.getUid() == 1000) {
+                    detectedLossEventForSystemServer = true;
+                    // but if loss happens it should be annotated with predefined error code == 1
+                    assertThat(lossStats.getLastError())
+                            .isEqualTo(LIB_STATS_SOCKET_QUEUE_OVERFLOW_ERROR_CODE);
+                }
+            }
+
+            assertThat(detectedLossEventForAppBreadcrumbAtom).isTrue();
+            assertThat(detectedLossEventForSystemServer).isFalse();
+
+            boolean detectedLossEventForAppBreadcrumbAtomViaSocketLossStats = false;
+            for (LossStatsPerUid lossStats : report.getSocketLossStats().getLossStatsPerUidList()) {
+                for (AtomIdLossStats atomLossStats : lossStats.getAtomIdLossStatsList()) {
+                    if (atomLossStats.getAtomId() == Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) {
+                        detectedLossEventForAppBreadcrumbAtomViaSocketLossStats = true;
+                    }
+                }
+            }
+            assertThat(detectedLossEventForAppBreadcrumbAtomViaSocketLossStats).isTrue();
+        }
+    }
+
+    /** Test libstatssocket logging queue atom id distribution collection */
+    public void testAtomIdLossDistributionCollection() throws Exception {
+        if (!ApiLevelUtil.codenameEquals(getDevice(), "VanillaIceCream")) {
+            return;
+        }
+
+        final String appTestApk = "StatsdAtomStormApp.apk";
+        final String app2TestApk = "StatsdAtomStormApp2.apk";
+
+        final String appTestPkg = "com.android.statsd.app.atomstorm";
+        final String app2TestPkg = "com.android.statsd.app.atomstorm.copy";
+
+        DeviceUtils.uninstallTestApp(getDevice(), appTestPkg);
+        DeviceUtils.uninstallTestApp(getDevice(), app2TestPkg);
+
+        DeviceUtils.installTestApp(getDevice(), appTestApk, appTestPkg, mCtsBuild);
+        DeviceUtils.installTestApp(getDevice(), app2TestApk, app2TestPkg, mCtsBuild);
+
+        // run reference test app with UID 1
+        DeviceUtils.runDeviceTests(getDevice(), appTestPkg, null, null);
+        // run reference test app with UID 2
+        DeviceUtils.runDeviceTests(getDevice(), app2TestPkg, null, null);
+
+        StatsdStatsReport report = getStatsdStatsReport();
+        assertThat(report).isNotNull();
+        HashSet<Integer> reportedUids = new HashSet<Integer>();
+        for (LossStatsPerUid lossStats : report.getSocketLossStats().getLossStatsPerUidList()) {
+            reportedUids.add(lossStats.getUid());
+        }
+        assertThat(reportedUids.size()).isGreaterThan(1);
+
+        getDevice().uninstallPackage(appTestPkg);
+        getDevice().uninstallPackage(app2TestPkg);
+    }
 }
diff --git a/tests/src/android/cts/statsd/metric/CountMetricsTests.java b/tests/src/android/cts/statsd/metric/CountMetricsTests.java
index 22b4f9c..8e9b5a3 100644
--- a/tests/src/android/cts/statsd/metric/CountMetricsTests.java
+++ b/tests/src/android/cts/statsd/metric/CountMetricsTests.java
@@ -18,48 +18,83 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
 import com.android.internal.os.StatsdConfigProto.Position;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.BleScanStateChanged;
 import com.android.os.AtomsProto.WakelockStateChanged;
 import com.android.os.AttributionNode;
-import com.android.os.StatsLog;
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.CountBucketInfo;
 import com.android.os.StatsLog.CountMetricData;
 import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
-import java.util.Arrays;
+import com.google.protobuf.ExtensionRegistry;
+
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
-public class CountMetricsTests extends DeviceAtomTestCase {
+public class CountMetricsTests extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     public void testSimpleEventCountMetric() throws Exception {
         int matcherId = 1;
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         builder.addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
-                .setId(MetricsUtils.COUNT_METRIC_ID)
-                .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                .setWhat(matcherId))
+                        .setId(MetricsUtils.COUNT_METRIC_ID)
+                        .setBucket(StatsdConfigProto.TimeUnit.ONE_MINUTE)
+                        .setWhat(matcherId))
                 .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId));
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(100);
-        doAppBreadcrumbReportedStop(0);
-        Thread.sleep(2000);  // Wait for the metrics to propagate to statsd.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
+        RunUtil.getDefault().sleep(100);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 0);
+        RunUtil.getDefault().sleep(2000);  // Wait for the metrics to propagate to statsd.
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
         assertThat(metricReport.hasCountMetrics()).isTrue();
@@ -69,6 +104,7 @@
         assertThat(countData.getDataCount()).isGreaterThan(0);
         assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
     }
+
     public void testEventCountWithCondition() throws Exception {
         int startMatcherId = 1;
         int endMatcherId = 2;
@@ -76,7 +112,7 @@
         int conditionId = 4;
 
         StatsdConfigProto.AtomMatcher whatMatcher =
-               MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
+                MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
 
         StatsdConfigProto.AtomMatcher predicateStartMatcher =
                 MetricsUtils.startAtomMatcher(startMatcherId);
@@ -92,7 +128,8 @@
                 .setId(conditionId)
                 .build();
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
                         .setId(MetricsUtils.COUNT_METRIC_ID)
                         .setBucket(StatsdConfigProto.TimeUnit.CTS)
@@ -103,20 +140,26 @@
                 .addAtomMatcher(predicateEndMatcher)
                 .addPredicate(p);
 
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
-        doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-        Thread.sleep(10);
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(10);
-        doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-        Thread.sleep(10);
-        doAppBreadcrumbReportedStop(0);
-        Thread.sleep(10);
-        doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
-        Thread.sleep(2000);  // Wait for the metrics to propagate to statsd.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 0);
+        RunUtil.getDefault().sleep(2000);  // Wait for the metrics to propagate to statsd.
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
         assertThat(metricReport.hasCountMetrics()).isTrue();
 
@@ -149,7 +192,7 @@
 
         StatsdConfigProto.AtomMatcher activationMatcher =
                 MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
-                                                           activationMatcherLabel);
+                        activationMatcherLabel);
 
         StatsdConfigProto.Predicate p = StatsdConfigProto.Predicate.newBuilder()
                 .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
@@ -159,10 +202,11 @@
                 .setId(conditionId)
                 .build();
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
                         .setId(MetricsUtils.COUNT_METRIC_ID)
-                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                        .setBucket(StatsdConfigProto.TimeUnit.ONE_MINUTE)
                         .setWhat(whatMatcherId)
                         .setCondition(conditionId)
                 )
@@ -178,66 +222,79 @@
                                 .setAtomMatcherId(activationMatcherId)
                                 .setTtlSeconds(ttlSec)));
 
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to true.
-        doAppBreadcrumbReportedStart(startMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), startMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Log an event that should be counted. Bucket 1 Count 1.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Log an event that should be counted. Bucket 1 Count 2.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to false.
-        doAppBreadcrumbReportedStop(endMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), endMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Log an event that should not be counted because condition is false.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Log an event that should not be counted.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Condition to true again.
-        doAppBreadcrumbReportedStart(startMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), startMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Event should not be counted, metric is still not active.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         //  Log an event that should be counted.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Log an event that should not be counted.
-        doAppBreadcrumbReported(whatMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), whatMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.hasCountMetrics()).isTrue();
@@ -252,27 +309,30 @@
 
     public void testPartialBucketCountMetric() throws Exception {
         int matcherId = 1;
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-        builder
-            .addCountMetric(
-                StatsdConfigProto.CountMetric.newBuilder()
-                    .setId(MetricsUtils.COUNT_METRIC_ID)
-                    .setBucket(StatsdConfigProto.TimeUnit.ONE_DAY) // Ensures partial bucket.
-                    .setWhat(matcherId)
-                    .setSplitBucketForAppUpgrade(true))
-            .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId));
-        uploadConfig(builder);
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
+                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
+                        .setId(MetricsUtils.COUNT_METRIC_ID)
+                        .setBucket(StatsdConfigProto.TimeUnit.ONE_DAY) // Ensures partial bucket.
+                        .setWhat(matcherId)
+                        .setSplitBucketForAppUpgrade(true))
+                .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId));
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
-        doAppBreadcrumbReportedStart(0);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
 
         builder.getCountMetricBuilder(0).setBucket(StatsdConfigProto.TimeUnit.CTS);
-        uploadConfig(builder);  // The count metric had a partial bucket.
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(10);
-        doAppBreadcrumbReportedStart(0);
-        Thread.sleep(WAIT_TIME_LONG); // Finish the current bucket.
+        ConfigUtils.uploadConfig(getDevice(), builder);  // The count metric had a partial bucket.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); // Finish the current bucket.
 
-        ConfigMetricsReportList reports = getReportList();
+        ConfigMetricsReportList reports = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Got following report list: " + reports.toString());
 
         assertThat(reports.getReportsCount()).isEqualTo(2);
@@ -285,9 +345,9 @@
             assertThat(report.getMetrics(0).getCountMetrics().getDataCount()).isEqualTo(1);
         }
         CountMetricData data1 =
-                reports.getReports(inOrder? 0 : 1).getMetrics(0).getCountMetrics().getData(0);
+                reports.getReports(inOrder ? 0 : 1).getMetrics(0).getCountMetrics().getData(0);
         CountMetricData data2 =
-                reports.getReports(inOrder? 1 : 0).getMetrics(0).getCountMetrics().getData(0);
+                reports.getReports(inOrder ? 1 : 0).getMetrics(0).getCountMetrics().getData(0);
         // Data1 should have only 1 bucket, and it should be a partial bucket.
         // The count should be 1.
         assertThat(data1.getBucketInfoCount()).isEqualTo(1);
@@ -326,90 +386,93 @@
                         .build();
 
         StatsdConfigProto.State state = StatsdConfigProto.State.newBuilder()
-            .setId(stateId)
-            .setAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
-            .setMap(StatsdConfigProto.StateMap.newBuilder()
-                    .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
-                            .setGroupId(onStateGroupId)
-                            .addValue(WakelockStateChanged.State.ACQUIRE_VALUE)
-                            .addValue(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE)
-                    )
-                    .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
-                            .setGroupId(offStateGroupId)
-                            .addValue(WakelockStateChanged.State.RELEASE_VALUE)
-                            .addValue(WakelockStateChanged.State.CHANGE_RELEASE_VALUE)
-                    )
-            )
-            .build();
+                .setId(stateId)
+                .setAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
+                .setMap(StatsdConfigProto.StateMap.newBuilder()
+                        .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
+                                .setGroupId(onStateGroupId)
+                                .addValue(WakelockStateChanged.State.ACQUIRE_VALUE)
+                                .addValue(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE)
+                        )
+                        .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder()
+                                .setGroupId(offStateGroupId)
+                                .addValue(WakelockStateChanged.State.RELEASE_VALUE)
+                                .addValue(WakelockStateChanged.State.CHANGE_RELEASE_VALUE)
+                        )
+                )
+                .build();
 
         StatsdConfigProto.MetricStateLink stateLink = StatsdConfigProto.MetricStateLink.newBuilder()
-            .setStateAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
-            .setFieldsInWhat(FieldMatcher.newBuilder()
-                    .setField(whatAtomId)
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(1)
-                            .setPosition(Position.FIRST)
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(AttributionNode.UID_FIELD_NUMBER)
-                            )
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(2)
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(3)
-                    )
-            )
-            .setFieldsInState(FieldMatcher.newBuilder()
-                    .setField(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(WakelockStateChanged.ATTRIBUTION_NODE_FIELD_NUMBER)
-                            .setPosition(Position.FIRST)
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(AttributionNode.UID_FIELD_NUMBER)
-                            )
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
-                    )
-                    .addChild(FieldMatcher.newBuilder()
-                            .setField(WakelockStateChanged.TAG_FIELD_NUMBER)
-                    )
-            )
-            .build();
-
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
-                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
-                    .setId(MetricsUtils.COUNT_METRIC_ID)
-                    .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                    .setWhat(whatMatcherId)
-                    .addSliceByState(stateId)
-                    .addStateLink(stateLink)
-                    .setDimensionsInWhat(
-                        FieldMatcher.newBuilder()
-                            .setField(whatAtomId)
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(1)
-                                    .setPosition(Position.FIRST)
-                                    .addChild(FieldMatcher.newBuilder()
-                                            .setField(AttributionNode.UID_FIELD_NUMBER)
-                                    )
-                            )
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(2)
-                            )
-                            .addChild(FieldMatcher.newBuilder()
-                                    .setField(3)
+                .setStateAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
+                .setFieldsInWhat(FieldMatcher.newBuilder()
+                        .setField(whatAtomId)
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(1)
+                                .setPosition(Position.FIRST)
+                                .addChild(FieldMatcher.newBuilder()
+                                        .setField(AttributionNode.UID_FIELD_NUMBER)
+                                )
                         )
-                    )
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(2)
+                        )
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(3)
+                        )
+                )
+                .setFieldsInState(FieldMatcher.newBuilder()
+                        .setField(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(WakelockStateChanged.ATTRIBUTION_NODE_FIELD_NUMBER)
+                                .setPosition(Position.FIRST)
+                                .addChild(FieldMatcher.newBuilder()
+                                        .setField(AttributionNode.UID_FIELD_NUMBER)
+                                )
+                        )
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(WakelockStateChanged.TYPE_FIELD_NUMBER)
+                        )
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(WakelockStateChanged.TAG_FIELD_NUMBER)
+                        )
+                )
+                .build();
+
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
+                .addCountMetric(StatsdConfigProto.CountMetric.newBuilder()
+                        .setId(MetricsUtils.COUNT_METRIC_ID)
+                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                        .setWhat(whatMatcherId)
+                        .addSliceByState(stateId)
+                        .addStateLink(stateLink)
+                        .setDimensionsInWhat(
+                                FieldMatcher.newBuilder()
+                                        .setField(whatAtomId)
+                                        .addChild(FieldMatcher.newBuilder()
+                                                .setField(1)
+                                                .setPosition(Position.FIRST)
+                                                .addChild(FieldMatcher.newBuilder()
+                                                        .setField(AttributionNode.UID_FIELD_NUMBER)
+                                                )
+                                        )
+                                        .addChild(FieldMatcher.newBuilder()
+                                                .setField(2)
+                                        )
+                                        .addChild(FieldMatcher.newBuilder()
+                                                .setField(3)
+                                        )
+                        )
                 )
                 .addAtomMatcher(whatMatcher)
                 .addState(state);
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSliceByWakelockState");
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, ".AtomTests",
+                "testSliceByWakelockState");
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
         assertThat(metricReport.hasCountMetrics()).isTrue();
@@ -420,10 +483,10 @@
 
         List<CountMetricData> sortedDataList = IntStream.range(0, dataWrapper.getDataCount())
                 .mapToObj(i -> {
-                        CountMetricData data = dataWrapper.getData(i);
-                        assertWithMessage("Unexpected SliceByState count for data[%s]", "" + i)
-                                .that(data.getSliceByStateCount()).isEqualTo(1);
-                        return data;
+                    CountMetricData data = dataWrapper.getData(i);
+                    assertWithMessage("Unexpected SliceByState count for data[%s]", "" + i)
+                            .that(data.getSliceByStateCount()).isEqualTo(1);
+                    return data;
                 })
                 .sorted((data1, data2) ->
                         Long.compare(data1.getSliceByState(0).getGroupId(),
diff --git a/tests/src/android/cts/statsd/metric/DurationMetricsTests.java b/tests/src/android/cts/statsd/metric/DurationMetricsTests.java
index 65cef95..89c9bbc 100644
--- a/tests/src/android/cts/statsd/metric/DurationMetricsTests.java
+++ b/tests/src/android/cts/statsd/metric/DurationMetricsTests.java
@@ -17,45 +17,62 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
-import com.android.internal.os.StatsdConfigProto.Position;
 import com.android.internal.os.StatsdConfigProto.Predicate;
-import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
 import com.android.internal.os.StatsdConfigProto.SimplePredicate;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
 import com.android.os.AtomsProto.Atom;
-import com.android.os.StatsLog.ConfigMetricsReport;
-import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.DurationBucketInfo;
 import com.android.os.StatsLog.StatsLogReport;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.RunUtil;
 
 import com.google.common.collect.Range;
+import com.google.protobuf.ExtensionRegistry;
 
-import java.util.List;
 
-public class DurationMetricsTests extends DeviceAtomTestCase {
+public class DurationMetricsTests extends DeviceTestCase {
 
     private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
     private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
     private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
     private static final int APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID = 3;
 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        super.tearDown();
+    }
+
     public void testDurationMetric() throws Exception {
         final int label = 1;
         // Add AtomMatchers.
         AtomMatcher startAtomMatcher =
-            MetricsUtils.startAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, label);
+                MetricsUtils.startAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID,
+                        label);
         AtomMatcher stopAtomMatcher =
-            MetricsUtils.stopAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, label);
+                MetricsUtils.stopAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID,
+                        label);
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         builder.addAtomMatcher(startAtomMatcher);
         builder.addAtomMatcher(stopAtomMatcher);
 
@@ -65,31 +82,33 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
         builder.addPredicate(predicate);
 
         // Add DurationMetric.
-        builder.addDurationMetric(
-            StatsdConfigProto.DurationMetric.newBuilder()
+        builder.addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
                 .setId(MetricsUtils.DURATION_METRIC_ID)
                 .setWhat(predicate.getId())
                 .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
                 .setBucket(StatsdConfigProto.TimeUnit.CTS));
 
         // Upload config.
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Create AppBreadcrumbReported Start/Stop events.
-        doAppBreadcrumbReportedStart(label);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStop(label);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), label);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), label);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.hasDurationMetrics()).isTrue();
@@ -97,7 +116,7 @@
                 = metricReport.getDurationMetrics();
         assertThat(durationData.getDataCount()).isEqualTo(1);
         assertThat(durationData.getData(0).getBucketInfo(0).getDurationNanos())
-                .isIn(Range.open(0L, (long)1e9));
+                .isIn(Range.open(0L, (long) 1e9));
     }
 
     public void testDurationMetricWithCondition() throws Exception {
@@ -114,7 +133,8 @@
         AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
                 APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID, conditionLabel);
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        DeviceUtils.STATSD_ATOM_TEST_PKG)
                 .addAtomMatcher(startAtomMatcher)
                 .addAtomMatcher(stopAtomMatcher)
                 .addAtomMatcher(conditionStartAtomMatcher)
@@ -126,76 +146,82 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
 
         SimplePredicate conditionSimplePredicate = SimplePredicate.newBuilder()
                 .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
                 .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
                 .build();
         Predicate conditionPredicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("ConditionPredicate"))
-                                  .setSimplePredicate(conditionSimplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("ConditionPredicate"))
+                .setSimplePredicate(conditionSimplePredicate)
+                .build();
 
-        builder
-            .addPredicate(predicate)
-            .addPredicate(conditionPredicate);
+        builder.addPredicate(predicate).addPredicate(conditionPredicate);
 
         // Add DurationMetric.
-        builder
-                .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
-                        .setId(MetricsUtils.DURATION_METRIC_ID)
-                        .setWhat(predicate.getId())
-                        .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
-                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                        .setCondition(conditionPredicate.getId())
-                );
+        builder.addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
+                .setId(MetricsUtils.DURATION_METRIC_ID)
+                .setWhat(predicate.getId())
+                .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
+                .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                .setCondition(conditionPredicate.getId())
+        );
 
         // Upload config.
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
-        StatsLogReport metricReport = getStatsLogReport();
+        RunUtil.getDefault().sleep(2_000);
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.hasDurationMetrics()).isTrue();
@@ -204,9 +230,9 @@
         assertThat(durationData.getDataCount()).isEqualTo(1);
         long totalDuration = durationData.getData(0).getBucketInfoList().stream()
                 .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
-                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
+                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long) 1e9)))
                 .sum();
-        assertThat(totalDuration).isIn(Range.open((long)2e9, (long)3e9));
+        assertThat(totalDuration).isIn(Range.open((long) 2e9, (long) 3e9));
     }
 
     public void testDurationMetricWithActivation() throws Exception {
@@ -222,9 +248,10 @@
                 APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
         StatsdConfigProto.AtomMatcher activationMatcher =
                 MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
-                                                           activationMatcherLabel);
+                        activationMatcherLabel);
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addAtomMatcher(startAtomMatcher)
                 .addAtomMatcher(stopAtomMatcher)
                 .addAtomMatcher(activationMatcher);
@@ -235,19 +262,17 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
         builder.addPredicate(predicate);
 
         // Add DurationMetric.
-        builder
-                .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
+        builder.addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
                         .setId(MetricsUtils.DURATION_METRIC_ID)
                         .setWhat(predicate.getId())
                         .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
-                        .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                )
+                        .setBucket(StatsdConfigProto.TimeUnit.CTS))
                 .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
                         .setMetricId(MetricsUtils.DURATION_METRIC_ID)
                         .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
@@ -257,34 +282,40 @@
                                 .setTtlSeconds(ttlSec)));
 
         // Upload config.
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
-        StatsLogReport metricReport = getStatsLogReport();
+        RunUtil.getDefault().sleep(2_000);
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.hasDurationMetrics()).isTrue();
@@ -293,9 +324,9 @@
         assertThat(durationData.getDataCount()).isEqualTo(1);
         long totalDuration = durationData.getData(0).getBucketInfoList().stream()
                 .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
-                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
+                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long) 1e9)))
                 .sum();
-        assertThat(totalDuration).isIn(Range.open((long)2e9, (long)3e9));
+        assertThat(totalDuration).isIn(Range.open((long) 2e9, (long) 3e9));
     }
 
     public void testDurationMetricWithConditionAndActivation() throws Exception {
@@ -316,9 +347,10 @@
                 APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID, conditionLabel);
         StatsdConfigProto.AtomMatcher activationMatcher =
                 MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
-                                                           activationMatcherLabel);
+                        activationMatcherLabel);
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addAtomMatcher(startAtomMatcher)
                 .addAtomMatcher(stopAtomMatcher)
                 .addAtomMatcher(conditionStartAtomMatcher)
@@ -331,9 +363,9 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
         builder.addPredicate(predicate);
 
         SimplePredicate conditionSimplePredicate = SimplePredicate.newBuilder()
@@ -341,20 +373,18 @@
                 .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
                 .build();
         Predicate conditionPredicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("ConditionPredicate"))
-                                  .setSimplePredicate(conditionSimplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("ConditionPredicate"))
+                .setSimplePredicate(conditionSimplePredicate)
+                .build();
         builder.addPredicate(conditionPredicate);
 
         // Add DurationMetric.
-        builder
-                .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
+        builder.addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
                         .setId(MetricsUtils.DURATION_METRIC_ID)
                         .setWhat(predicate.getId())
                         .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
                         .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                        .setCondition(conditionPredicate.getId())
-                )
+                        .setCondition(conditionPredicate.getId()))
                 .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
                         .setMetricId(MetricsUtils.DURATION_METRIC_ID)
                         .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
@@ -364,100 +394,118 @@
                                 .setTtlSeconds(ttlSec)));
 
         // Upload config.
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
         //doAppBreadcrumbReported(99); // TODO: maybe remove?
-        //Thread.sleep(10);
+        //RunUtil.getDefault().sleep(10);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set condition to true again.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
-        // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        // Start uncounted duration
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Activate the metric.
-        doAppBreadcrumbReported(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Start counted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop counted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Start uncounted duration.
-        doAppBreadcrumbReportedStart(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
-        Thread.sleep(2_000);
+        RunUtil.getDefault().sleep(2_000);
 
         // Stop uncounted duration.
-        doAppBreadcrumbReportedStop(durationLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), durationLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
         assertThat(metricReport.hasDurationMetrics()).isTrue();
@@ -466,23 +514,24 @@
         assertThat(durationData.getDataCount()).isEqualTo(1);
         long totalDuration = durationData.getData(0).getBucketInfoList().stream()
                 .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
-                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
+                .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long) 1e9)))
                 .sum();
-        assertThat(totalDuration).isIn(Range.open((long)4e9, (long)5e9));
+        assertThat(totalDuration).isIn(Range.open((long) 4e9, (long) 5e9));
     }
 
     public void testDurationMetricWithDimension() throws Exception {
         // Add AtomMatchers.
         AtomMatcher startAtomMatcherA =
-            MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
+                MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
         AtomMatcher stopAtomMatcherA =
-            MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
+                MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
         AtomMatcher startAtomMatcherB =
-            MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
+                MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
         AtomMatcher stopAtomMatcherB =
-            MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID);
+                MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID);
 
-        StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         builder.addAtomMatcher(startAtomMatcherA);
         builder.addAtomMatcher(stopAtomMatcherA);
         builder.addAtomMatcher(startAtomMatcherB);
@@ -494,9 +543,9 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicateA = Predicate.newBuilder()
-                                   .setId(MetricsUtils.StringToId("Predicate_A"))
-                                   .setSimplePredicate(simplePredicateA)
-                                   .build();
+                .setId(MetricsUtils.StringToId("Predicate_A"))
+                .setSimplePredicate(simplePredicateA)
+                .build();
         builder.addPredicate(predicateA);
 
         FieldMatcher.Builder dimensionsBuilder = FieldMatcher.newBuilder()
@@ -504,47 +553,50 @@
         dimensionsBuilder
                 .addChild(FieldMatcher.newBuilder().setField(
                         AppBreadcrumbReported.LABEL_FIELD_NUMBER));
-        Predicate predicateB =
-            Predicate.newBuilder()
+        Predicate predicateB = Predicate.newBuilder()
                 .setId(MetricsUtils.StringToId("Predicate_B"))
                 .setSimplePredicate(SimplePredicate.newBuilder()
-                                        .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                                        .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
-                                        .setDimensions(dimensionsBuilder.build())
-                                        .build())
+                        .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                        .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
+                        .setDimensions(dimensionsBuilder.build())
+                        .build())
                 .build();
         builder.addPredicate(predicateB);
 
         // Add DurationMetric.
-        builder.addDurationMetric(
-            StatsdConfigProto.DurationMetric.newBuilder()
+        builder.addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
                 .setId(MetricsUtils.DURATION_METRIC_ID)
                 .setWhat(predicateB.getId())
                 .setCondition(predicateA.getId())
                 .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
                 .setBucket(StatsdConfigProto.TimeUnit.CTS)
                 .setDimensionsInWhat(
-                    FieldMatcher.newBuilder()
-                            .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    AppBreadcrumbReported.LABEL_FIELD_NUMBER))));
+                        FieldMatcher.newBuilder()
+                                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                .addChild(FieldMatcher.newBuilder().setField(
+                                        AppBreadcrumbReported.LABEL_FIELD_NUMBER))));
 
         // Upload config.
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Trigger events.
-        doAppBreadcrumbReportedStart(1);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStart(2);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStop(1);
-        Thread.sleep(2000);
-        doAppBreadcrumbReportedStop(2);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 2);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 2);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
         assertThat(metricReport.hasDurationMetrics()).isTrue();
         StatsLogReport.DurationMetricDataWrapper durationData
diff --git a/tests/src/android/cts/statsd/metric/GaugeMetricsTests.java b/tests/src/android/cts/statsd/metric/GaugeMetricsTests.java
index db6a818..a037a69 100644
--- a/tests/src/android/cts/statsd/metric/GaugeMetricsTests.java
+++ b/tests/src/android/cts/statsd/metric/GaugeMetricsTests.java
@@ -17,7 +17,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.ActivationType;
@@ -25,7 +27,6 @@
 import com.android.internal.os.StatsdConfigProto.EventActivation;
 import com.android.internal.os.StatsdConfigProto.FieldFilter;
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
 import com.android.internal.os.StatsdConfigProto.GaugeMetric;
 import com.android.internal.os.StatsdConfigProto.MetricActivation;
 import com.android.internal.os.StatsdConfigProto.Predicate;
@@ -35,166 +36,205 @@
 import com.android.internal.os.StatsdConfigProto.TimeUnit;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
 import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog;
 import com.android.os.StatsLog.GaugeBucketInfo;
 import com.android.os.StatsLog.GaugeMetricData;
 import com.android.os.StatsLog.StatsLogReport;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.Pair;
+import com.android.tradefed.util.RunUtil;
 
-public class GaugeMetricsTests extends DeviceAtomTestCase {
+import com.google.protobuf.ExtensionRegistry;
 
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
-  private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
 
-  public void testGaugeMetric() throws Exception {
-      // Add AtomMatcher's.
-      AtomMatcher startAtomMatcher =
-          MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
-      AtomMatcher stopAtomMatcher =
-          MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
-      AtomMatcher atomMatcher =
-          MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
+public class GaugeMetricsTests extends DeviceTestCase {
 
-      StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-      builder.addAtomMatcher(startAtomMatcher);
-      builder.addAtomMatcher(stopAtomMatcher);
-      builder.addAtomMatcher(atomMatcher);
+    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
+    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
+    private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
 
-      // Add Predicate's.
-      SimplePredicate simplePredicate = SimplePredicate.newBuilder()
-                                            .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
-                                            .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
-                                            .build();
-      Predicate predicate = Predicate.newBuilder()
-                                .setId(MetricsUtils.StringToId("Predicate"))
-                                .setSimplePredicate(simplePredicate)
-                                .build();
-      builder.addPredicate(predicate);
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        RunUtil.getDefault().sleep(1000);
+    }
 
-      // Add GaugeMetric.
-      FieldMatcher fieldMatcher =
-          FieldMatcher.newBuilder().setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID).build();
-      builder.addGaugeMetric(
-          StatsdConfigProto.GaugeMetric.newBuilder()
-              .setId(MetricsUtils.GAUGE_METRIC_ID)
-              .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-              .setCondition(predicate.getId())
-              .setGaugeFieldsFilter(
-                  FieldFilter.newBuilder().setIncludeAll(false).setFields(fieldMatcher).build())
-              .setDimensionsInWhat(
-                  FieldMatcher.newBuilder()
-                      .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                      .addChild(FieldMatcher.newBuilder()
-                                    .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                    .build())
-                      .build())
-              .setBucket(StatsdConfigProto.TimeUnit.CTS)
-              .build());
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        super.tearDown();
+    }
 
-      // Upload config.
-      uploadConfig(builder);
+    public void testGaugeMetric() throws Exception {
+        // Add AtomMatcher's.
+        AtomMatcher startAtomMatcher =
+                MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
+        AtomMatcher stopAtomMatcher =
+                MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
+        AtomMatcher atomMatcher =
+                MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
 
-      // Create AppBreadcrumbReported Start/Stop events.
-      doAppBreadcrumbReportedStart(0);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStart(1);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStart(2);
-      Thread.sleep(2000);
-      doAppBreadcrumbReportedStop(2);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStop(0);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStop(1);
-      doAppBreadcrumbReportedStart(2);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStart(1);
-      Thread.sleep(2000);
-      doAppBreadcrumbReportedStop(2);
-      Thread.sleep(10);
-      doAppBreadcrumbReportedStop(1);
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        builder.addAtomMatcher(startAtomMatcher);
+        builder.addAtomMatcher(stopAtomMatcher);
+        builder.addAtomMatcher(atomMatcher);
 
-      // Wait for the metrics to propagate to statsd.
-      Thread.sleep(2000);
+        // Add Predicate's.
+        SimplePredicate simplePredicate = SimplePredicate.newBuilder()
+                .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
+                .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
+                .build();
+        Predicate predicate = Predicate.newBuilder()
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
+        builder.addPredicate(predicate);
 
-      StatsLogReport metricReport = getStatsLogReport();
-      LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
-      assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
-      assertThat(metricReport.hasGaugeMetrics()).isTrue();
-      StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
-      gaugeData = backfillGaugeMetricData(gaugeData);
-      assertThat(gaugeData.getDataCount()).isEqualTo(1);
+        // Add GaugeMetric.
+        FieldMatcher fieldMatcher = FieldMatcher.newBuilder()
+                .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                .build();
+        builder.addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder()
+                .setId(MetricsUtils.GAUGE_METRIC_ID)
+                .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                .setCondition(predicate.getId())
+                .setGaugeFieldsFilter(FieldFilter.newBuilder()
+                        .setIncludeAll(false)
+                        .setFields(fieldMatcher)
+                        .build())
+                .setDimensionsInWhat(FieldMatcher.newBuilder()
+                        .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                        .addChild(FieldMatcher.newBuilder()
+                                .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                .build())
+                        .build())
+                .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                .build());
 
-      int bucketCount = gaugeData.getData(0).getBucketInfoCount();
-      GaugeMetricData data = gaugeData.getData(0);
-      assertThat(bucketCount).isGreaterThan(2);
-      MetricsUtils.assertBucketTimePresent(data.getBucketInfo(0));
-      assertThat(data.getBucketInfo(0).getAtomCount()).isEqualTo(1);
-      assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel())
-              .isEqualTo(0);
-      assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState())
-              .isEqualTo(AppBreadcrumbReported.State.START);
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
-      MetricsUtils.assertBucketTimePresent(data.getBucketInfo(1));
-      assertThat(data.getBucketInfo(1).getAtomCount()).isEqualTo(1);
+        // Create AppBreadcrumbReported Start/Stop events.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 2);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 2);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 0);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 2);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(2000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 2);
+        RunUtil.getDefault().sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
 
-      MetricsUtils.assertBucketTimePresent(data.getBucketInfo(bucketCount-1));
-      assertThat(data.getBucketInfo(bucketCount-1).getAtomCount()).isEqualTo(1);
-      assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getLabel())
-              .isEqualTo(2);
-      assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getState())
-              .isEqualTo(AppBreadcrumbReported.State.STOP);
-  }
+        // Wait for the metrics to propagate to statsd.
+        RunUtil.getDefault().sleep(2000);
 
-  public void testPulledGaugeMetricWithActivation() throws Exception {
-      // Add AtomMatcher's.
-      int activationAtomMatcherId = 1;
-      int activationAtomMatcherLabel = 1;
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
+        assertThat(metricReport.hasGaugeMetrics()).isTrue();
+        StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
+        gaugeData = backfillGaugeMetricData(gaugeData);
+        assertThat(gaugeData.getDataCount()).isEqualTo(1);
 
-      int systemUptimeMatcherId = 2;
-      AtomMatcher activationAtomMatcher =
-              MetricsUtils.appBreadcrumbMatcherWithLabel(
-                      activationAtomMatcherId, activationAtomMatcherLabel);
-      AtomMatcher systemUptimeMatcher =
-              AtomMatcher.newBuilder()
-                      .setId(systemUptimeMatcherId)
-                      .setSimpleAtomMatcher(
-                              SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_UPTIME_FIELD_NUMBER))
-                      .build();
+        int bucketCount = gaugeData.getData(0).getBucketInfoCount();
+        GaugeMetricData data = gaugeData.getData(0);
+        assertThat(bucketCount).isGreaterThan(2);
+        MetricsUtils.assertBucketTimePresent(data.getBucketInfo(0));
+        assertThat(data.getBucketInfo(0).getAtomCount()).isEqualTo(1);
+        assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel())
+                .isEqualTo(0);
+        assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState())
+                .isEqualTo(AppBreadcrumbReported.State.START);
 
-      StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
-      builder.addAtomMatcher(activationAtomMatcher);
-      builder.addAtomMatcher(systemUptimeMatcher);
+        MetricsUtils.assertBucketTimePresent(data.getBucketInfo(1));
+        assertThat(data.getBucketInfo(1).getAtomCount()).isEqualTo(1);
 
-      // Add GaugeMetric.
-      builder.addGaugeMetric(
-              StatsdConfigProto.GaugeMetric.newBuilder()
-                      .setId(MetricsUtils.GAUGE_METRIC_ID)
-                      .setWhat(systemUptimeMatcherId)
-                      .setGaugeFieldsFilter(
-                              FieldFilter.newBuilder().setIncludeAll(true).build())
-                      .setBucket(StatsdConfigProto.TimeUnit.CTS)
-                      .build());
+        MetricsUtils.assertBucketTimePresent(data.getBucketInfo(bucketCount - 1));
+        assertThat(data.getBucketInfo(bucketCount - 1).getAtomCount()).isEqualTo(1);
+        assertThat(data.getBucketInfo(bucketCount - 1).getAtom(
+                0).getAppBreadcrumbReported().getLabel())
+                .isEqualTo(2);
+        assertThat(data.getBucketInfo(bucketCount - 1).getAtom(
+                0).getAppBreadcrumbReported().getState())
+                .isEqualTo(AppBreadcrumbReported.State.STOP);
+    }
 
-      // Add activation.
-      builder.addMetricActivation(MetricActivation.newBuilder()
-              .setMetricId(MetricsUtils.GAUGE_METRIC_ID)
-              .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-              .addEventActivation(EventActivation.newBuilder()
-                    .setAtomMatcherId(activationAtomMatcherId)
-                    .setTtlSeconds(5)));
+    public void testPulledGaugeMetricWithActivation() throws Exception {
+        // Add AtomMatcher's.
+        int activationAtomMatcherId = 1;
+        int activationAtomMatcherLabel = 1;
 
-      // Upload config.
-      uploadConfig(builder);
+        int systemUptimeMatcherId = 2;
+        AtomMatcher activationAtomMatcher = MetricsUtils.appBreadcrumbMatcherWithLabel(
+                activationAtomMatcherId, activationAtomMatcherLabel);
+        AtomMatcher systemUptimeMatcher = AtomMatcher.newBuilder()
+                .setId(systemUptimeMatcherId)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.SYSTEM_UPTIME_FIELD_NUMBER))
+                .build();
 
-      // Plenty of time to pull, but we should not keep the data since we are not active.
-      Thread.sleep(20_000);
+        StatsdConfigProto.StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        builder.addAtomMatcher(activationAtomMatcher);
+        builder.addAtomMatcher(systemUptimeMatcher);
 
-      StatsLogReport metricReport = getStatsLogReport();
-      LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
-      assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
-      assertThat(metricReport.hasGaugeMetrics()).isFalse();
-  }
+        // Add GaugeMetric.
+        builder.addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder()
+                .setId(MetricsUtils.GAUGE_METRIC_ID)
+                .setWhat(systemUptimeMatcherId)
+                .setGaugeFieldsFilter(
+                        FieldFilter.newBuilder().setIncludeAll(true).build())
+                .setBucket(StatsdConfigProto.TimeUnit.CTS)
+                .build());
+
+        // Add activation.
+        builder.addMetricActivation(MetricActivation.newBuilder()
+                .setMetricId(MetricsUtils.GAUGE_METRIC_ID)
+                .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
+                .addEventActivation(EventActivation.newBuilder()
+                        .setAtomMatcherId(activationAtomMatcherId)
+                        .setTtlSeconds(5)));
+
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
+
+        // Plenty of time to pull, but we should not keep the data since we are not active.
+        RunUtil.getDefault().sleep(20_000);
+
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
+        assertThat(metricReport.hasGaugeMetrics()).isFalse();
+    }
 
     public void testPulledGaugeMetricWithConditionAndActivation() throws Exception {
         final int conditionLabel = 2;
@@ -208,17 +248,16 @@
                 APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, conditionLabel);
         AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
                 APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, conditionLabel);
-        AtomMatcher activationMatcher =
-                MetricsUtils.startAtomMatcherWithLabel(
-                        activationMatcherId, activationMatcherLabel);
-        AtomMatcher whatMatcher =
-                MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
+        AtomMatcher activationMatcher = MetricsUtils.startAtomMatcherWithLabel(
+                activationMatcherId, activationMatcherLabel);
+        AtomMatcher whatMatcher = MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
 
-        StatsdConfig.Builder builder = createConfigBuilder()
-                .addAtomMatcher(conditionStartAtomMatcher)
-                .addAtomMatcher(conditionStopAtomMatcher)
-                .addAtomMatcher(whatMatcher)
-                .addAtomMatcher(activationMatcher);
+        StatsdConfig.Builder builder =
+                ConfigUtils.createConfigBuilder(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
+                        .addAtomMatcher(conditionStartAtomMatcher)
+                        .addAtomMatcher(conditionStopAtomMatcher)
+                        .addAtomMatcher(whatMatcher)
+                        .addAtomMatcher(activationMatcher);
 
         // Add Predicates.
         SimplePredicate simplePredicate = SimplePredicate.newBuilder()
@@ -226,95 +265,102 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
         builder.addPredicate(predicate);
 
         // Add GaugeMetric.
-        builder
-                .addGaugeMetric(GaugeMetric.newBuilder()
+        builder.addGaugeMetric(GaugeMetric.newBuilder()
                         .setId(MetricsUtils.GAUGE_METRIC_ID)
                         .setWhat(whatMatcher.getId())
                         .setBucket(TimeUnit.CTS)
                         .setCondition(predicate.getId())
-                        .setGaugeFieldsFilter(
-                                FieldFilter.newBuilder().setIncludeAll(false).setFields(
-                                        FieldMatcher.newBuilder()
-                                                .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                                )
-                        )
-                        .setDimensionsInWhat(FieldMatcher.newBuilder().setField(whatMatcherId))
-                )
+                        .setGaugeFieldsFilter(FieldFilter.newBuilder()
+                                .setIncludeAll(false)
+                                .setFields(FieldMatcher.newBuilder()
+                                        .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)))
+                        .setDimensionsInWhat(FieldMatcher.newBuilder().setField(whatMatcherId)))
                 .addMetricActivation(MetricActivation.newBuilder()
                         .setMetricId(MetricsUtils.GAUGE_METRIC_ID)
                         .addEventActivation(EventActivation.newBuilder()
                                 .setAtomMatcherId(activationMatcherId)
                                 .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-                                .setTtlSeconds(ttlSec)
-                        )
-                );
+                                .setTtlSeconds(ttlSec)));
 
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // This value is collected.
-        doAppBreadcrumbReported(10);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 10);
+        RunUtil.getDefault().sleep(10);
 
         // Ignored; value already collected.
-        doAppBreadcrumbReported(20);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 20);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Value not updated because condition is false.
-        doAppBreadcrumbReported(30);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 30);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Value not collected.
-        doAppBreadcrumbReported(40);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 40);
+        RunUtil.getDefault().sleep(10);
 
         // Condition to true again.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Value not collected.
-        doAppBreadcrumbReported(50);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 50);
+        RunUtil.getDefault().sleep(10);
 
         // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Value collected.
-        doAppBreadcrumbReported(60);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 60);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Value not collected.
-        doAppBreadcrumbReported(70);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 70);
+        RunUtil.getDefault().sleep(10);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
         assertThat(metricReport.hasGaugeMetrics()).isTrue();
@@ -335,4 +381,42 @@
         assertThat(bucketInfo.getAtomCount()).isEqualTo(1);
         assertThat(bucketInfo.getAtom(0).getAppBreadcrumbReported().getLabel()).isEqualTo(60);
     }
+
+    private StatsLogReport.GaugeMetricDataWrapper backfillGaugeMetricData(
+            StatsLogReport.GaugeMetricDataWrapper dataWrapper) {
+        StatsLogReport.GaugeMetricDataWrapper.Builder dataWrapperBuilder = dataWrapper.toBuilder();
+        List<GaugeMetricData> backfilledMetricData = new ArrayList<>();
+        for (GaugeMetricData gaugeMetricData : dataWrapperBuilder.getDataList()) {
+            GaugeMetricData.Builder gaugeMetricDataBuilder = gaugeMetricData.toBuilder();
+            List<GaugeBucketInfo> backfilledBuckets = new ArrayList<>();
+            for (GaugeBucketInfo bucketInfo : gaugeMetricData.getBucketInfoList()) {
+                backfilledBuckets.add(backfillGaugeBucket(bucketInfo.toBuilder()));
+            }
+            gaugeMetricDataBuilder.clearBucketInfo();
+            gaugeMetricDataBuilder.addAllBucketInfo(backfilledBuckets);
+            backfilledMetricData.add(gaugeMetricDataBuilder.build());
+        }
+        dataWrapperBuilder.clearData();
+        dataWrapperBuilder.addAllData(backfilledMetricData);
+        return dataWrapperBuilder.build();
+    }
+
+    private GaugeBucketInfo backfillGaugeBucket(GaugeBucketInfo.Builder bucketInfoBuilder) {
+        if (bucketInfoBuilder.getAtomCount() != 0) {
+            return bucketInfoBuilder.build();
+        }
+        List<Pair<Atom, Long>> atomTimestampData = new ArrayList<>();
+        for (StatsLog.AggregatedAtomInfo atomInfo : bucketInfoBuilder.getAggregatedAtomInfoList()) {
+            for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) {
+                atomTimestampData.add(Pair.create(atomInfo.getAtom(), timestampNs));
+            }
+        }
+        atomTimestampData.sort(Comparator.comparing(o -> o.second));
+        bucketInfoBuilder.clearAggregatedAtomInfo();
+        for (Pair<Atom, Long> atomTimestamp : atomTimestampData) {
+            bucketInfoBuilder.addAtom(atomTimestamp.first);
+            bucketInfoBuilder.addElapsedTimestampNanos(atomTimestamp.second);
+        }
+        return bucketInfoBuilder.build();
+    }
 }
diff --git a/tests/src/android/cts/statsd/metric/MetricActivationTests.java b/tests/src/android/cts/statsd/metric/MetricActivationTests.java
index 06f95b9..5c6f2fb 100644
--- a/tests/src/android/cts/statsd/metric/MetricActivationTests.java
+++ b/tests/src/android/cts/statsd/metric/MetricActivationTests.java
@@ -16,32 +16,39 @@
 package android.cts.statsd.metric;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
-import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.ActivationType;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.EventActivation;
 import com.android.internal.os.StatsdConfigProto.EventMetric;
-import com.android.internal.os.StatsdConfigProto.GaugeMetric;
 import com.android.internal.os.StatsdConfigProto.MetricActivation;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
-import com.android.os.AtomsProto.Atom;
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.EventMetricData;
 import com.android.os.StatsLog.StatsLogReport;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.RunUtil;
+
+import com.google.protobuf.ExtensionRegistry;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Test Statsd Metric activations and deactivations
  */
-public class MetricActivationTests extends DeviceAtomTestCase {
+public class MetricActivationTests extends DeviceTestCase {
     private final long metric1Id = 1L;
     private final int metric1MatcherId = 1;
 
@@ -57,6 +64,20 @@
     private final int act2MatcherId = 20;
     private final int act2CancelMatcherId = -20;
 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        super.tearDown();
+    }
 
     private StatsdConfig.Builder createConfig(final int act1TtlSecs, final int act2TtlSecs) {
         AtomMatcher metric1Matcher =
@@ -65,49 +86,41 @@
                 MetricsUtils.simpleAtomMatcher(metric2MatcherId, metric2MatcherId);
         AtomMatcher metric3Matcher =
                 MetricsUtils.simpleAtomMatcher(metric3MatcherId, metric3MatcherId);
-        AtomMatcher act1Matcher =
-                MetricsUtils.simpleAtomMatcher(act1MatcherId, act1MatcherId);
+        AtomMatcher act1Matcher = MetricsUtils.simpleAtomMatcher(act1MatcherId, act1MatcherId);
         AtomMatcher act1CancelMatcher =
                 MetricsUtils.simpleAtomMatcher(act1CancelMatcherId, act1CancelMatcherId);
-        AtomMatcher act2Matcher =
-                MetricsUtils.simpleAtomMatcher(act2MatcherId, act2MatcherId);
+        AtomMatcher act2Matcher = MetricsUtils.simpleAtomMatcher(act2MatcherId, act2MatcherId);
         AtomMatcher act2CancelMatcher =
                 MetricsUtils.simpleAtomMatcher(act2CancelMatcherId, act2CancelMatcherId);
 
-        EventMetric metric1 = EventMetric.newBuilder()
-                .setId(metric1Id)
-                .setWhat(metric1MatcherId)
-                .build();
+        EventMetric metric1 =
+                EventMetric.newBuilder().setId(metric1Id).setWhat(metric1MatcherId).build();
 
-        EventMetric metric2 = EventMetric.newBuilder()
-                .setId(metric2Id)
-                .setWhat(metric2MatcherId)
-                .build();
+        EventMetric metric2 =
+                EventMetric.newBuilder().setId(metric2Id).setWhat(metric2MatcherId).build();
 
-        EventMetric metric3 = EventMetric.newBuilder()
-                .setId(metric3Id)
-                .setWhat(metric3MatcherId)
-                .build();
+        EventMetric metric3 =
+                EventMetric.newBuilder().setId(metric3Id).setWhat(metric3MatcherId).build();
 
         EventActivation metric1Act1 =
                 MetricsUtils.createEventActivation(act1TtlSecs, act1MatcherId, act1CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-                    .build();
+                        .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
+                        .build();
 
         EventActivation metric1Act2 =
                 MetricsUtils.createEventActivation(act2TtlSecs, act2MatcherId, act2CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
-                    .build();
+                        .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
+                        .build();
 
         EventActivation metric2Act1 =
                 MetricsUtils.createEventActivation(act1TtlSecs, act1MatcherId, act1CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
-                    .build();
+                        .setActivationType(ActivationType.ACTIVATE_ON_BOOT)
+                        .build();
 
         EventActivation metric2Act2 =
                 MetricsUtils.createEventActivation(act2TtlSecs, act2MatcherId, act2CancelMatcherId)
-                    .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-                    .build();
+                        .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
+                        .build();
 
         MetricActivation metric1Activation = MetricActivation.newBuilder()
                 .setMetricId(metric1Id)
@@ -121,7 +134,8 @@
                 .addEventActivation(metric2Act2)
                 .build();
 
-        return createConfigBuilder()
+
+        return ConfigUtils.createConfigBuilder(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addAtomMatcher(metric1Matcher)
                 .addAtomMatcher(metric2Matcher)
                 .addAtomMatcher(metric3Matcher)
@@ -138,96 +152,113 @@
 
     /**
      * Metric 1:
-     *     Activation 1:
-     *         - Ttl: 5 seconds
-     *         - Type: IMMEDIATE
-     *     Activation 2:
-     *         - Ttl: 8 seconds
-     *         - Type: ON_BOOT
+     * Activation 1:
+     * - Ttl: 5 seconds
+     * - Type: IMMEDIATE
+     * Activation 2:
+     * - Ttl: 8 seconds
+     * - Type: ON_BOOT
      *
      * Metric 2:
-     *     Activation 1:
-     *         - Ttl: 5 seconds
-     *         - Type: ON_BOOT
-     *     Activation 2:
-     *         - Ttl: 8 seconds
-     *         - Type: IMMEDIATE
+     * Activation 1:
+     * - Ttl: 5 seconds
+     * - Type: ON_BOOT
+     * Activation 2:
+     * - Ttl: 8 seconds
+     * - Type: IMMEDIATE
      *
      * Metric 3: No activations; always active
      **/
     public void testCancellation() throws Exception {
         final int act1TtlSecs = 5;
         final int act2TtlSecs = 8;
-        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
+        ConfigUtils.uploadConfig(getDevice(), createConfig(act1TtlSecs, act2TtlSecs));
 
         // Ignored, metric not active.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Trigger cancel for already inactive event activation 1.
-        doAppBreadcrumbReported(act1CancelMatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1CancelMatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Trigger event activation 1.
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // First logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Second logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Cancel event activation 1.
-        doAppBreadcrumbReported(act1CancelMatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1CancelMatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Ignored, metric not active.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Trigger event activation 1.
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Trigger event activation 2.
-        doAppBreadcrumbReported(act2MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act2MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Third logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Cancel event activation 2.
-        doAppBreadcrumbReported(act2CancelMatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act2CancelMatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Fourth logged event.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Expire event activation 1
-        Thread.sleep(act1TtlSecs * 1000);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000);
 
         // Ignored, metric 1 not active. Activation 1 expired and Activation 2 was cancelled.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Trigger event activation 2.
-        doAppBreadcrumbReported(act2MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act2MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Metric 1 log ignored, Activation 1 expired and Activation 2 needs reboot to activate.
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // First logged event for Metric 3.
-        doAppBreadcrumbReported(metric3MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric3MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
-        ConfigMetricsReportList reportList = getReportList();
+        ConfigMetricsReportList reportList = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
         ConfigMetricsReport report = reports.get(0);
         verifyMetrics(report, 4, 0, 1);
@@ -235,27 +266,27 @@
 
     /**
      * Metric 1:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: IMMEDIATE
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: ON_BOOT
+     * Activation 1:
+     * - Ttl: 100 seconds
+     * - Type: IMMEDIATE
+     * Activation 2:
+     * - Ttl: 200 seconds
+     * - Type: ON_BOOT
      *
      * Metric 2:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: ON_BOOT
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: IMMEDIATE
+     * Activation 1:
+     * - Ttl: 100 seconds
+     * - Type: ON_BOOT
+     * Activation 2:
+     * - Ttl: 200 seconds
+     * - Type: IMMEDIATE
      *
      * Metric 3: No activations; always active
      **/
     public void testRestart() throws Exception {
         final int act1TtlSecs = 200;
         final int act2TtlSecs = 400;
-        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
+        ConfigUtils.uploadConfig(getDevice(), createConfig(act1TtlSecs, act2TtlSecs));
 
         // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
         // Time remaining:
@@ -263,8 +294,9 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // First logged event for Metric 1.
         // Metric 2 event ignored, will activate after boot.
@@ -276,7 +308,7 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 200 seconds
         // Metric 2 Activation 2: 0 seconds
-        rebootDeviceAndWaitUntilReady();
+        DeviceUtils.rebootDeviceAndWaitUntilReady(getDevice());
 
         // Second logged event for Metric 1.
         // First logged event for Metric 2.
@@ -288,7 +320,7 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds
         // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act1TtlSecs * 1000L);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000L);
 
         // Metric 1 event ignored, Activation 1 expired.
         // Metric 2 event ignored, Activation 1 expired.
@@ -301,8 +333,9 @@
         // Metric 1 Activation 2: 0 seconds (will activate after boot)
         // Metric 2 Activation 1: 0 seconds
         // Metric 2 Activation 2: 400 seconds
-        doAppBreadcrumbReported(act2MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act2MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Metric 1 event ignored, will activate after boot.
         // Second logged event for Metric 2.
@@ -315,8 +348,9 @@
         // Metric 1 Activation 2: 0 seconds (will activate after boot)
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 400 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Third logged event for Metric 1.
         // Third logged event for Metric 2.
@@ -328,14 +362,14 @@
         // Metric 1 Activation 2: 0 seconds (will activate after boot)
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 300 seconds
-        Thread.sleep(act1TtlSecs * 1000L / 2);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000L / 2);
 
         // Time remaining:
         // Metric 1 Activation 1: 100 seconds
         // Metric 1 Activation 2: 400 seconds
         // Metric 2 Activation 1: 200 seconds
         // Metric 2 Activation 2: 300 seconds
-        rebootDeviceAndWaitUntilReady();
+        DeviceUtils.rebootDeviceAndWaitUntilReady(getDevice());
 
         // Fourth logged event for Metric 1.
         // Fourth logged event for Metric 2.
@@ -348,7 +382,7 @@
         // Metric 1 Activation 2: 300 seconds
         // Metric 2 Activation 1: 100 seconds
         // Metric 2 Activation 2: 200 seconds
-        Thread.sleep(act1TtlSecs * 1000L / 2);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000L / 2);
 
         // Fifth logged event for Metric 1.
         // Fifth logged event for Metric 2.
@@ -361,14 +395,15 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds
         // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act2TtlSecs * 1000L);
+        RunUtil.getDefault().sleep(act2TtlSecs * 1000L);
 
         // Metric 1 event ignored.
         // Metric 2 event ignored.
         // Eighth logged event for Metric 3.
         logAllMetrics();
 
-        ConfigMetricsReportList reportList = getReportList();
+        ConfigMetricsReportList reportList = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
         assertThat(reports).hasSize(3);
 
@@ -387,27 +422,27 @@
 
     /**
      * Metric 1:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: IMMEDIATE
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: ON_BOOT
+     * Activation 1:
+     * - Ttl: 100 seconds
+     * - Type: IMMEDIATE
+     * Activation 2:
+     * - Ttl: 200 seconds
+     * - Type: ON_BOOT
      *
      * Metric 2:
-     *     Activation 1:
-     *         - Ttl: 100 seconds
-     *         - Type: ON_BOOT
-     *     Activation 2:
-     *         - Ttl: 200 seconds
-     *         - Type: IMMEDIATE
+     * Activation 1:
+     * - Ttl: 100 seconds
+     * - Type: ON_BOOT
+     * Activation 2:
+     * - Ttl: 200 seconds
+     * - Type: IMMEDIATE
      *
      * Metric 3: No activations; always active
      **/
     public void testMultipleActivations() throws Exception {
         final int act1TtlSecs = 200;
         final int act2TtlSecs = 400;
-        uploadConfig(createConfig(act1TtlSecs, act2TtlSecs));
+        ConfigUtils.uploadConfig(getDevice(), createConfig(act1TtlSecs, act2TtlSecs));
 
         // Trigger Metric 1 Activation 1 and Metric 2 Activation 1.
         // Time remaining:
@@ -415,8 +450,9 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // First logged event for Metric 1.
         // Metric 2 event ignored, will activate after boot.
@@ -428,7 +464,7 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act1TtlSecs * 1000L / 2);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000L / 2);
 
         // Second logged event for Metric 1.
         // Metric 2 event ignored, will activate after boot.
@@ -441,8 +477,9 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds (will activate after boot)
         // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Third logged event for Metric 1.
         // Metric 2 event ignored, will activate after boot.
@@ -454,7 +491,7 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 200 seconds
         // Metric 2 Activation 2: 0 seconds
-        rebootDeviceAndWaitUntilReady();
+        DeviceUtils.rebootDeviceAndWaitUntilReady(getDevice());
 
         // Fourth logged event for Metric 1.
         // First logged event for Metric 2.
@@ -467,8 +504,9 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 200 seconds
         // Metric 2 Activation 2: 0 seconds
-        doAppBreadcrumbReported(act1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), act1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
         // Fifth logged event for Metric 1.
         // Second logged event for Metric 2.
@@ -481,7 +519,7 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds
         // Metric 2 Activation 2: 0 seconds
-        Thread.sleep(act1TtlSecs * 1000L);
+        RunUtil.getDefault().sleep(act1TtlSecs * 1000L);
 
         // Metric 1 event ignored.
         // Metric 2 event ignored.
@@ -493,15 +531,16 @@
         // Metric 1 Activation 2: 0 seconds
         // Metric 2 Activation 1: 0 seconds
         // Metric 2 Activation 2: 0 seconds
-        rebootDeviceAndWaitUntilReady();
-        Thread.sleep(6_000L);
+        DeviceUtils.rebootDeviceAndWaitUntilReady(getDevice());
+        RunUtil.getDefault().sleep(10_000L);
 
         // Metric 1 event ignored.
         // Metric 2 event ignored.
         // Seventh logged event for Metric 3.
         logAllMetrics();
 
-        ConfigMetricsReportList reportList = getReportList();
+        ConfigMetricsReportList reportList = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
         assertThat(reports).hasSize(3);
 
@@ -518,35 +557,45 @@
         verifyMetrics(report, 0, 0, 1);
     }
 
+    /**
+     * Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
+     */
+    private List<ConfigMetricsReport> getSortedConfigMetricsReports(
+            ConfigMetricsReportList configMetricsReportList) {
+        return configMetricsReportList.getReportsList().stream().sorted(
+                Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos)).collect(
+                Collectors.toList());
+    }
+
     private void logAllMetrics() throws Exception {
-        doAppBreadcrumbReported(metric1MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric1MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
-        doAppBreadcrumbReported(metric2MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric2MatcherId);
+        RunUtil.getDefault().sleep(10L);
 
-        doAppBreadcrumbReported(metric3MatcherId);
-        Thread.sleep(10L);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), metric3MatcherId);
+        RunUtil.getDefault().sleep(10L);
     }
 
     private void verifyMetrics(ConfigMetricsReport report, int metric1Count, int metric2Count,
             int metric3Count) throws Exception {
         assertThat(report.getMetricsCount()).isEqualTo(3);
 
-        verifyMetric(
-                report.getMetrics(0),   // StatsLogReport
+        verifyMetric(report.getMetrics(0),   // StatsLogReport
                 1,                      // Metric Id
                 1,                      // Metric what atom matcher label
                 metric1Count            // Data count
         );
-        verifyMetric(
-                report.getMetrics(1),   // StatsLogReport
+        verifyMetric(report.getMetrics(1),   // StatsLogReport
                 2,                      // Metric Id
                 2,                      // Metric what atom matcher label
                 metric2Count            // Data count
         );
-        verifyMetric(
-                report.getMetrics(2),   // StatsLogReport
+        verifyMetric(report.getMetrics(2),   // StatsLogReport
                 3,                      // Metric Id
                 3,                      // Metric what atom matcher label
                 metric3Count            // Data count
@@ -562,7 +611,8 @@
         StatsLogReport.EventMetricDataWrapper eventData = metricReport.getEventMetrics();
         List<EventMetricData> eventMetricDataList = new ArrayList<>();
         for (EventMetricData eventMetricData : eventData.getDataList()) {
-            eventMetricDataList.addAll(backfillAggregatedAtomsInEventMetric(eventMetricData));
+            eventMetricDataList.addAll(
+                    ReportUtils.backfillAggregatedAtomsInEventMetric(eventMetricData));
         }
         assertThat(eventMetricDataList).hasSize(dataCount);
         for (EventMetricData eventMetricData : eventMetricDataList) {
diff --git a/tests/src/android/cts/statsd/metric/MetricsUtils.java b/tests/src/android/cts/statsd/metric/MetricsUtils.java
index 8f559d2..010df49 100644
--- a/tests/src/android/cts/statsd/metric/MetricsUtils.java
+++ b/tests/src/android/cts/statsd/metric/MetricsUtils.java
@@ -17,6 +17,8 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.cts.statsd.atom.BufferDebug;
+
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.EventActivation;
@@ -24,11 +26,26 @@
 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.util.RunUtil;
+
+import com.google.protobuf.InvalidProtocolBufferException;
 import com.google.protobuf.Message;
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
 
 public class MetricsUtils {
+    public static final String DEVICE_SIDE_TEST_PACKAGE =
+            "com.android.server.cts.device.statsd";
+    public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
     public static final long COUNT_METRIC_ID = 3333;
     public static final long DURATION_METRIC_ID = 4444;
     public static final long GAUGE_METRIC_ID = 5555;
@@ -38,20 +55,19 @@
     public static AtomMatcher.Builder getAtomMatcher(int atomId) {
         AtomMatcher.Builder builder = AtomMatcher.newBuilder();
         builder.setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                        .setAtomId(atomId));
+                .setAtomId(atomId));
         return builder;
     }
 
     public static AtomMatcher startAtomMatcher(int id) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(
-              SimpleAtomMatcher.newBuilder()
-                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                            .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                            .setEqInt(AppBreadcrumbReported.State.START.ordinal())))
-          .build();
+        return AtomMatcher.newBuilder()
+                .setId(id)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                .setEqInt(AppBreadcrumbReported.State.START.getNumber())))
+                .build();
     }
 
     public static AtomMatcher startAtomMatcherWithLabel(int id, int label) {
@@ -59,15 +75,14 @@
     }
 
     public static AtomMatcher stopAtomMatcher(int id) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(
-              SimpleAtomMatcher.newBuilder()
-                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                                            .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                            .setEqInt(AppBreadcrumbReported.State.STOP.ordinal())))
-          .build();
+        return AtomMatcher.newBuilder()
+                .setId(id)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
+                                .setEqInt(AppBreadcrumbReported.State.STOP.getNumber())))
+                .build();
     }
 
     public static AtomMatcher stopAtomMatcherWithLabel(int id, int label) {
@@ -81,16 +96,16 @@
                         .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
                         .addFieldValueMatcher(FieldValueMatcher.newBuilder()
                                 .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                .setEqInt(AppBreadcrumbReported.State.UNSPECIFIED.ordinal())))
+                                .setEqInt(AppBreadcrumbReported.State.UNSPECIFIED.getNumber())))
                 .build();
     }
 
     public static AtomMatcher simpleAtomMatcher(int id) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(
-              SimpleAtomMatcher.newBuilder().setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
-          .build();
+        return AtomMatcher.newBuilder()
+                .setId(id)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
+                .build();
     }
 
     public static AtomMatcher appBreadcrumbMatcherWithLabel(int id, int label) {
@@ -113,7 +128,7 @@
                         .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
                         .addFieldValueMatcher(FieldValueMatcher.newBuilder()
                                 .setField(AppBreadcrumbReported.STATE_FIELD_NUMBER)
-                                .setEqInt(state.ordinal()))
+                                .setEqInt(state.getNumber()))
                         .addFieldValueMatcher(FieldValueMatcher.newBuilder()
                                 .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
                                 .setEqInt(label)))
@@ -121,16 +136,16 @@
     }
 
     public static AtomMatcher simpleAtomMatcher(int id, int label) {
-      return AtomMatcher.newBuilder()
-          .setId(id)
-          .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
-                  .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                  .addFieldValueMatcher(FieldValueMatcher.newBuilder()
-                            .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
-                            .setEqInt(label)
-                  )
-          )
-          .build();
+        return AtomMatcher.newBuilder()
+                .setId(id)
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
+                                .setEqInt(label)
+                        )
+                )
+                .build();
     }
 
     public static EventActivation.Builder createEventActivation(int ttlSecs, int matcherId,
@@ -142,7 +157,14 @@
     }
 
     public static long StringToId(String str) {
-      return str.hashCode();
+        return str.hashCode();
+    }
+
+    public static String getCurrentLogcatDate(ITestDevice device) throws Exception {
+        // TODO: Do something more robust than this for getting logcat markers.
+        long timestampMs = device.getDeviceDate();
+        return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
+                .format(new Date(timestampMs));
     }
 
     public static void assertBucketTimePresent(Message bucketInfo) {
@@ -154,11 +176,60 @@
         if (bucketNum != null && bucketInfo.hasField(bucketNum)) {
             found = true;
         } else if (startMillis != null && bucketInfo.hasField(startMillis) &&
-                   endMillis != null && bucketInfo.hasField(endMillis)) {
+                endMillis != null && bucketInfo.hasField(endMillis)) {
             found = true;
         }
         assertWithMessage(
                 "Bucket info did not have either bucket num or start and end elapsed millis"
         ).that(found).isTrue();
     }
+
+    public static boolean didIncidentdFireSince(ITestDevice device, String date) throws Exception {
+        final String INCIDENTD_TAG = "incidentd";
+        final String INCIDENTD_STARTED_STRING = "reportIncident";
+        // TODO: Do something more robust than this in case of delayed logging.
+        RunUtil.getDefault().sleep(1000);
+        String log = getLogcatSince(device, date, String.format(
+                "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
+        return log.contains(INCIDENTD_STARTED_STRING);
+    }
+
+    public static String getLogcatSince(ITestDevice device, String date, String logcatParams)
+            throws Exception {
+        return device.executeShellCommand(String.format(
+                "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
+    }
+
+    /**
+     * Call onto the device with an adb shell command and get the results of
+     * that as a proto of the given type.
+     *
+     * @param parser  A protobuf parser object. e.g. MyProto.parser()
+     * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
+     * @throws DeviceNotAvailableException    If there was a problem communicating with
+     *                                        the test device.
+     * @throws InvalidProtocolBufferException If there was an error parsing
+     *                                        the proto. Note that a 0 length buffer is not
+     *                                        necessarily an error.
+     */
+    public static <T extends MessageLite> T getDump(ITestDevice device, Parser<T> parser,
+            String command)
+            throws DeviceNotAvailableException, InvalidProtocolBufferException {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        device.executeShellCommand(command, receiver);
+        if (false) {
+            LogUtil.CLog.d("Command output while parsing " + parser.getClass().getCanonicalName()
+                    + " for command: " + command + "\n"
+                    + BufferDebug.debugString(receiver.getOutput(), -1));
+        }
+        try {
+            return parser.parseFrom(receiver.getOutput());
+        } catch (Exception ex) {
+            LogUtil.CLog.d(
+                    "Error parsing " + parser.getClass().getCanonicalName() + " for command: "
+                            + command
+                            + BufferDebug.debugString(receiver.getOutput(), 16384));
+            throw ex;
+        }
+    }
 }
diff --git a/tests/src/android/cts/statsd/metric/ValueMetricsTests.java b/tests/src/android/cts/statsd/metric/ValueMetricsTests.java
index 3b4ef56..4ac1d5e 100644
--- a/tests/src/android/cts/statsd/metric/ValueMetricsTests.java
+++ b/tests/src/android/cts/statsd/metric/ValueMetricsTests.java
@@ -17,14 +17,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 
 import com.android.internal.os.StatsdConfigProto.ActivationType;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.EventActivation;
-import com.android.internal.os.StatsdConfigProto.FieldFilter;
 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
-import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
 import com.android.internal.os.StatsdConfigProto.MetricActivation;
 import com.android.internal.os.StatsdConfigProto.Predicate;
 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
@@ -41,286 +41,320 @@
 import com.android.os.StatsLog.ValueMetricData;
 
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.RunUtil;
 
-public class ValueMetricsTests extends DeviceAtomTestCase {
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
-  private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
-  private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
+import com.google.protobuf.ExtensionRegistry;
 
-  public void testValueMetric() throws Exception {
-    // Add AtomMatcher's.
-    AtomMatcher startAtomMatcher =
-        MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
-    AtomMatcher stopAtomMatcher =
-        MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
-    AtomMatcher atomMatcher =
-        MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
+public class ValueMetricsTests extends DeviceTestCase {
+    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
+    private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
+    private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
 
-    StatsdConfig.Builder builder = createConfigBuilder();
-    builder.addAtomMatcher(startAtomMatcher);
-    builder.addAtomMatcher(stopAtomMatcher);
-    builder.addAtomMatcher(atomMatcher);
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-        ValueMetric.newBuilder()
-            .setId(MetricsUtils.VALUE_METRIC_ID)
-            .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-            .setBucket(TimeUnit.CTS)
-            .setValueField(FieldMatcher.newBuilder()
-                               .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
-                               .addChild(FieldMatcher.newBuilder().setField(
-                                   AppBreadcrumbReported.LABEL_FIELD_NUMBER)))
-            .setDimensionsInWhat(FieldMatcher.newBuilder()
-                                     .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
-                                     .build())
-            .build());
-
-    // Upload config.
-    uploadConfig(builder);
-
-    // Create AppBreadcrumbReported Start/Stop events.
-    doAppBreadcrumbReportedStart(1);
-    Thread.sleep(1000);
-    doAppBreadcrumbReportedStop(1);
-    doAppBreadcrumbReportedStart(3);
-    doAppBreadcrumbReportedStop(3);
-
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1000);
-
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.hasValueMetrics()).isTrue();
-    StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertThat(valueData.getDataCount()).isEqualTo(1);
-
-    int bucketCount = valueData.getData(0).getBucketInfoCount();
-    assertThat(bucketCount).isGreaterThan(1);
-    ValueMetricData data = valueData.getData(0);
-    int totalValue = 0;
-    for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
-      MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
-      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
-      totalValue += (int) bucketInfo.getValues(0).getValueLong();
-    }
-    assertThat(totalValue).isEqualTo(8);
-  }
-
-  // Test value metric with pulled atoms and across multiple buckets
-  public void testPullerAcrossBuckets() throws Exception {
-    // Add AtomMatcher's.
-    final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
-    final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
-    final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
-
-    AtomMatcher startAtomMatcher =
-            MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
-    AtomMatcher stopAtomMatcher =
-            MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
-
-    StatsdConfig.Builder builder = createConfigBuilder();
-    builder.addAtomMatcher(startAtomMatcher);
-    builder.addAtomMatcher(stopAtomMatcher);
-    builder.addPredicate(Predicate.newBuilder()
-            .setId(predicateName.hashCode())
-            .setSimplePredicate(SimplePredicate.newBuilder()
-                    .setStart(predicateTrueName.hashCode())
-                    .setStop(predicateFalseName.hashCode())
-                    .setCountNesting(false)
-            )
-    );
-
-    final String atomName = "SYSTEM_ELAPSED_REALTIME";
-    SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
-    builder.addAtomMatcher(AtomMatcher.newBuilder()
-            .setId(atomName.hashCode())
-            .setSimpleAtomMatcher(sam));
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-            ValueMetric.newBuilder()
-                    .setId(MetricsUtils.VALUE_METRIC_ID)
-                    .setWhat(atomName.hashCode())
-                    .setBucket(TimeUnit.ONE_MINUTE)
-                    .setValueField(FieldMatcher.newBuilder()
-                            .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
-                    .setCondition(predicateName.hashCode())
-                    .build());
-
-    // Upload config.
-    uploadConfig(builder);
-
-    // Create AppBreadcrumbReported Start/Stop events.
-    doAppBreadcrumbReportedStart(1);
-    // Wait for 2 min and 1 sec to capture at least 2 buckets
-    Thread.sleep(2*60_000 + 10_000);
-    doAppBreadcrumbReportedStop(1);
-
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1_000);
-
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.hasValueMetrics()).isTrue();
-    StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertThat(valueData.getDataCount()).isEqualTo(1);
-
-    int bucketCount = valueData.getData(0).getBucketInfoCount();
-    // should have at least 2 buckets
-    assertThat(bucketCount).isAtLeast(2);
-    ValueMetricData data = valueData.getData(0);
-    int totalValue = 0;
-    for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
-      MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
-      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
-      totalValue += (int) bucketInfo.getValues(0).getValueLong();
-    }
-    // At most we lose one full min bucket
-    assertThat(totalValue).isGreaterThan(130_000 - 60_000);
-  }
-
-  // Test value metric with pulled atoms and across multiple buckets
-  public void testMultipleEventsPerBucket() throws Exception {
-    // Add AtomMatcher's.
-    final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
-    final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
-    final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
-
-    AtomMatcher startAtomMatcher =
-            MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
-    AtomMatcher stopAtomMatcher =
-            MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
-
-    StatsdConfig.Builder builder = createConfigBuilder();
-    builder.addAtomMatcher(startAtomMatcher);
-    builder.addAtomMatcher(stopAtomMatcher);
-    builder.addPredicate(Predicate.newBuilder()
-            .setId(predicateName.hashCode())
-            .setSimplePredicate(SimplePredicate.newBuilder()
-                    .setStart(predicateTrueName.hashCode())
-                    .setStop(predicateFalseName.hashCode())
-                    .setCountNesting(false)
-            )
-    );
-
-    final String atomName = "SYSTEM_ELAPSED_REALTIME";
-    SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
-    builder.addAtomMatcher(AtomMatcher.newBuilder()
-            .setId(atomName.hashCode())
-            .setSimpleAtomMatcher(sam));
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-            ValueMetric.newBuilder()
-                    .setId(MetricsUtils.VALUE_METRIC_ID)
-                    .setWhat(atomName.hashCode())
-                    .setBucket(TimeUnit.ONE_MINUTE)
-                    .setValueField(FieldMatcher.newBuilder()
-                            .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
-                    .setCondition(predicateName.hashCode())
-                    .build());
-
-    // Upload config.
-    uploadConfig(builder);
-
-    final int NUM_EVENTS = 10;
-    final long GAP_INTERVAL = 10_000;
-    // Create AppBreadcrumbReported Start/Stop events.
-    for (int i = 0; i < NUM_EVENTS; i ++) {
-      doAppBreadcrumbReportedStart(1);
-      Thread.sleep(GAP_INTERVAL);
-      doAppBreadcrumbReportedStop(1);
-      Thread.sleep(GAP_INTERVAL);
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        RunUtil.getDefault().sleep(1000);
     }
 
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1_000);
-
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.hasValueMetrics()).isTrue();
-    StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertThat(valueData.getDataCount()).isEqualTo(1);
-
-    int bucketCount = valueData.getData(0).getBucketInfoCount();
-    // should have at least 2 buckets
-    assertThat(bucketCount).isAtLeast(2);
-    ValueMetricData data = valueData.getData(0);
-    int totalValue = 0;
-    for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
-      MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
-      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
-      totalValue += (int) bucketInfo.getValues(0).getValueLong();
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        super.tearDown();
     }
-    // At most we lose one full min bucket
-    assertThat((long) totalValue).isGreaterThan(GAP_INTERVAL * NUM_EVENTS - 60_000);
-  }
-
-  // Test value metric with pulled atoms and across multiple buckets
-  public void testPullerAcrossBucketsWithActivation() throws Exception {
-    StatsdConfig.Builder builder = createConfigBuilder();
-
-    // Add AtomMatcher's.
-    int activationAtomMatcherId = 1;
-    int activationAtomMatcherLabel = 1;
-    AtomMatcher activationAtomMatcher =
-            MetricsUtils.appBreadcrumbMatcherWithLabel(
-                    activationAtomMatcherId, activationAtomMatcherLabel);
-    final String atomName = "SYSTEM_ELAPSED_REALTIME";
-    SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder()
-            .setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
-    builder.addAtomMatcher(activationAtomMatcher)
-            .addAtomMatcher(AtomMatcher.newBuilder()
-                    .setId(atomName.hashCode())
-                    .setSimpleAtomMatcher(sam));
-
-    // Add ValueMetric.
-    builder.addValueMetric(
-            ValueMetric.newBuilder()
-                    .setId(MetricsUtils.VALUE_METRIC_ID)
-                    .setWhat(atomName.hashCode())
-                    .setBucket(TimeUnit.ONE_MINUTE)
-                    .setValueField(FieldMatcher.newBuilder()
-                            .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
-                            .addChild(FieldMatcher.newBuilder().setField(
-                                    SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
-                    .build());
-    // Add activation.
-    builder.addMetricActivation(MetricActivation.newBuilder()
-          .setMetricId(MetricsUtils.VALUE_METRIC_ID)
-          .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
-          .addEventActivation(EventActivation.newBuilder()
-                  .setAtomMatcherId(activationAtomMatcherId)
-                  .setTtlSeconds(5)));
 
 
-    // Upload config.
-    uploadConfig(builder);
+    public void testValueMetric() throws Exception {
+        // Add AtomMatcher's.
+        AtomMatcher startAtomMatcher =
+                MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
+        AtomMatcher stopAtomMatcher =
+                MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
+        AtomMatcher atomMatcher =
+                MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
 
-    // Wait for 1 min and 10 sec to capture at least 1 bucket
-    Thread.sleep(60_000 + 10_000);
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        builder.addAtomMatcher(startAtomMatcher);
+        builder.addAtomMatcher(stopAtomMatcher);
+        builder.addAtomMatcher(atomMatcher);
 
-    // Wait for the metrics to propagate to statsd.
-    Thread.sleep(1_000);
+        // Add ValueMetric.
+        builder.addValueMetric(ValueMetric.newBuilder()
+                .setId(MetricsUtils.VALUE_METRIC_ID)
+                .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                .setBucket(TimeUnit.CTS)
+                .setValueField(FieldMatcher.newBuilder()
+                        .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder().setField(
+                                AppBreadcrumbReported.LABEL_FIELD_NUMBER)))
+                .setDimensionsInWhat(FieldMatcher.newBuilder()
+                        .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
+                        .build())
+                .build());
 
-    StatsLogReport metricReport = getStatsLogReport();
-    LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
-    assertThat(metricReport.getValueMetrics().getDataList()).isEmpty();
-    // Skipped buckets are not added when metric is empty.
-    assertThat(metricReport.getValueMetrics().getSkippedList()).isEmpty();
-  }
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
+
+        // Create AppBreadcrumbReported Start/Stop events.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(1000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 3);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 3);
+
+        // Wait for the metrics to propagate to statsd.
+        RunUtil.getDefault().sleep(1000);
+
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+        assertThat(metricReport.hasValueMetrics()).isTrue();
+        StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
+        assertThat(valueData.getDataCount()).isEqualTo(1);
+
+        int bucketCount = valueData.getData(0).getBucketInfoCount();
+        assertThat(bucketCount).isGreaterThan(1);
+        ValueMetricData data = valueData.getData(0);
+        int totalValue = 0;
+        for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
+            MetricsUtils.assertBucketTimePresent(bucketInfo);
+            assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+            assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
+            totalValue += (int) bucketInfo.getValues(0).getValueLong();
+        }
+        assertThat(totalValue).isEqualTo(8);
+    }
+
+    // Test value metric with pulled atoms and across multiple buckets
+    public void testPullerAcrossBuckets() throws Exception {
+        // Add AtomMatcher's.
+        final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
+        final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
+        final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
+
+        AtomMatcher startAtomMatcher =
+                MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
+        AtomMatcher stopAtomMatcher =
+                MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
+
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        builder.addAtomMatcher(startAtomMatcher);
+        builder.addAtomMatcher(stopAtomMatcher);
+        builder.addPredicate(Predicate.newBuilder()
+                .setId(predicateName.hashCode())
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                        .setStart(predicateTrueName.hashCode())
+                        .setStop(predicateFalseName.hashCode())
+                        .setCountNesting(false)
+                )
+        );
+
+        final String atomName = "SYSTEM_ELAPSED_REALTIME";
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(
+                Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
+        builder.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(atomName.hashCode())
+                .setSimpleAtomMatcher(sam));
+
+        // Add ValueMetric.
+        builder.addValueMetric(ValueMetric.newBuilder()
+                .setId(MetricsUtils.VALUE_METRIC_ID)
+                .setWhat(atomName.hashCode())
+                .setBucket(TimeUnit.ONE_MINUTE)
+                .setValueField(FieldMatcher.newBuilder()
+                        .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder().setField(
+                                SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
+                .setCondition(predicateName.hashCode())
+                .build());
+
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
+
+        // Create AppBreadcrumbReported Start/Stop events.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), 1);
+        // Wait for 2 min and 1 sec to capture at least 2 buckets
+        RunUtil.getDefault().sleep(2 * 60_000 + 10_000);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), 1);
+
+        // Wait for the metrics to propagate to statsd.
+        RunUtil.getDefault().sleep(1_000);
+
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+        assertThat(metricReport.hasValueMetrics()).isTrue();
+        StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
+        assertThat(valueData.getDataCount()).isEqualTo(1);
+
+        int bucketCount = valueData.getData(0).getBucketInfoCount();
+        // should have at least 2 buckets
+        assertThat(bucketCount).isAtLeast(2);
+        ValueMetricData data = valueData.getData(0);
+        int totalValue = 0;
+        for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
+            MetricsUtils.assertBucketTimePresent(bucketInfo);
+            assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+            assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
+            totalValue += (int) bucketInfo.getValues(0).getValueLong();
+        }
+        // At most we lose one full min bucket
+        assertThat(totalValue).isGreaterThan(130_000 - 60_000);
+    }
+
+    // Test value metric with pulled atoms and across multiple buckets
+    public void testMultipleEventsPerBucket() throws Exception {
+        // Add AtomMatcher's.
+        final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
+        final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
+        final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
+
+        AtomMatcher startAtomMatcher =
+                MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
+        AtomMatcher stopAtomMatcher =
+                MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
+
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        builder.addAtomMatcher(startAtomMatcher);
+        builder.addAtomMatcher(stopAtomMatcher);
+        builder.addPredicate(Predicate.newBuilder()
+                .setId(predicateName.hashCode())
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                        .setStart(predicateTrueName.hashCode())
+                        .setStop(predicateFalseName.hashCode())
+                        .setCountNesting(false)
+                )
+        );
+
+        final String atomName = "SYSTEM_ELAPSED_REALTIME";
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(
+                Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
+        builder.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(atomName.hashCode())
+                .setSimpleAtomMatcher(sam));
+
+        // Add ValueMetric.
+        builder.addValueMetric(ValueMetric.newBuilder()
+                .setId(MetricsUtils.VALUE_METRIC_ID)
+                .setWhat(atomName.hashCode())
+                .setBucket(TimeUnit.ONE_MINUTE)
+                .setValueField(FieldMatcher.newBuilder()
+                        .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder().setField(
+                                SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
+                .setCondition(predicateName.hashCode())
+                .build());
+
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
+
+        final int NUM_EVENTS = 10;
+        final long GAP_INTERVAL = 10_000;
+        // Create AppBreadcrumbReported Start/Stop events.
+        for (int i = 0; i < NUM_EVENTS; i++) {
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AppBreadcrumbReported.State.START.getNumber(), 1);
+            RunUtil.getDefault().sleep(GAP_INTERVAL);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AppBreadcrumbReported.State.STOP.getNumber(), 1);
+            RunUtil.getDefault().sleep(GAP_INTERVAL);
+        }
+
+        // Wait for the metrics to propagate to statsd.
+        RunUtil.getDefault().sleep(1_000);
+
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+        assertThat(metricReport.hasValueMetrics()).isTrue();
+        StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
+        assertThat(valueData.getDataCount()).isEqualTo(1);
+
+        int bucketCount = valueData.getData(0).getBucketInfoCount();
+        // should have at least 2 buckets
+        assertThat(bucketCount).isAtLeast(2);
+        ValueMetricData data = valueData.getData(0);
+        int totalValue = 0;
+        for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
+            MetricsUtils.assertBucketTimePresent(bucketInfo);
+            assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+            assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
+            totalValue += (int) bucketInfo.getValues(0).getValueLong();
+        }
+        // At most we lose one full min bucket
+        assertThat((long) totalValue).isGreaterThan(GAP_INTERVAL * NUM_EVENTS - 60_000);
+    }
+
+    // Test value metric with pulled atoms and across multiple buckets
+    public void testPullerAcrossBucketsWithActivation() throws Exception {
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+
+        // Add AtomMatcher's.
+        int activationAtomMatcherId = 1;
+        int activationAtomMatcherLabel = 1;
+        AtomMatcher activationAtomMatcher =
+                MetricsUtils.appBreadcrumbMatcherWithLabel(
+                        activationAtomMatcherId, activationAtomMatcherLabel);
+        final String atomName = "SYSTEM_ELAPSED_REALTIME";
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder()
+                .setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
+        builder.addAtomMatcher(activationAtomMatcher)
+                .addAtomMatcher(AtomMatcher.newBuilder()
+                        .setId(atomName.hashCode())
+                        .setSimpleAtomMatcher(sam));
+
+        // Add ValueMetric.
+        builder.addValueMetric(ValueMetric.newBuilder()
+                .setId(MetricsUtils.VALUE_METRIC_ID)
+                .setWhat(atomName.hashCode())
+                .setBucket(TimeUnit.ONE_MINUTE)
+                .setValueField(FieldMatcher.newBuilder()
+                        .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
+                        .addChild(FieldMatcher.newBuilder().setField(
+                                SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
+                .build());
+        // Add activation.
+        builder.addMetricActivation(MetricActivation.newBuilder()
+                .setMetricId(MetricsUtils.VALUE_METRIC_ID)
+                .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
+                .addEventActivation(EventActivation.newBuilder()
+                        .setAtomMatcherId(activationAtomMatcherId)
+                        .setTtlSeconds(5)));
+
+
+        // Upload config.
+        ConfigUtils.uploadConfig(getDevice(), builder);
+
+        // Wait for 1 min and 10 sec to capture at least 1 bucket
+        RunUtil.getDefault().sleep(60_000 + 10_000);
+
+        // Wait for the metrics to propagate to statsd.
+        RunUtil.getDefault().sleep(1_000);
+
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
+        LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+        assertThat(metricReport.getValueMetrics().getDataList()).isEmpty();
+        // Skipped buckets are not added when metric is empty.
+        assertThat(metricReport.getValueMetrics().getSkippedList()).isEmpty();
+    }
 
     public void testValueMetricWithConditionAndActivation() throws Exception {
         final int conditionLabel = 2;
@@ -340,7 +374,8 @@
         AtomMatcher whatMatcher =
                 MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
 
-        StatsdConfig.Builder builder = createConfigBuilder()
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                        MetricsUtils.DEVICE_SIDE_TEST_PACKAGE)
                 .addAtomMatcher(conditionStartAtomMatcher)
                 .addAtomMatcher(conditionStopAtomMatcher)
                 .addAtomMatcher(whatMatcher)
@@ -352,14 +387,13 @@
                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
                 .build();
         Predicate predicate = Predicate.newBuilder()
-                                  .setId(MetricsUtils.StringToId("Predicate"))
-                                  .setSimplePredicate(simplePredicate)
-                                  .build();
+                .setId(MetricsUtils.StringToId("Predicate"))
+                .setSimplePredicate(simplePredicate)
+                .build();
         builder.addPredicate(predicate);
 
         // Add ValueMetric.
-        builder
-                .addValueMetric(ValueMetric.newBuilder()
+        builder.addValueMetric(ValueMetric.newBuilder()
                         .setId(MetricsUtils.VALUE_METRIC_ID)
                         .setWhat(whatMatcher.getId())
                         .setBucket(TimeUnit.ONE_MINUTE)
@@ -380,66 +414,79 @@
                         )
                 );
 
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
 
         // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to true.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Skipped due to unknown condition at start of bucket.
-        doAppBreadcrumbReported(10);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 10);
+        RunUtil.getDefault().sleep(10);
 
         // Skipped due to unknown condition at start of bucket.
-        doAppBreadcrumbReported(200);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 200);
+        RunUtil.getDefault().sleep(10);
 
         // Set the condition to false.
-        doAppBreadcrumbReportedStop(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.STOP.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Log an event that should not be counted because condition is false.
-        doAppBreadcrumbReported(3_000);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 3_000);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Log an event that should not be counted.
-        doAppBreadcrumbReported(40_000);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 40_000);
+        RunUtil.getDefault().sleep(10);
 
         // Condition to true again.
-        doAppBreadcrumbReportedStart(conditionLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), conditionLabel);
+        RunUtil.getDefault().sleep(10);
 
         // Event should not be counted, metric is still not active.
-        doAppBreadcrumbReported(500_000);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 500_000);
+        RunUtil.getDefault().sleep(10);
 
         // Activate the metric.
-        doAppBreadcrumbReportedStart(activationMatcherLabel);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.START.getNumber(), activationMatcherLabel);
+        RunUtil.getDefault().sleep(10);
 
         //  Log an event that should be counted.
-        doAppBreadcrumbReported(6_000_000);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 6_000_000);
+        RunUtil.getDefault().sleep(10);
 
         // Let the metric deactivate.
-        Thread.sleep(ttlSec * 1000);
+        RunUtil.getDefault().sleep(ttlSec * 1000);
 
         // Log an event that should not be counted.
-        doAppBreadcrumbReported(70_000_000);
-        Thread.sleep(10);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 70_000_000);
+        RunUtil.getDefault().sleep(10);
 
         // Wait for the metrics to propagate to statsd.
-        Thread.sleep(2000);
+        RunUtil.getDefault().sleep(2000);
 
-        StatsLogReport metricReport = getStatsLogReport();
+        StatsLogReport metricReport = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
         assertThat(metricReport.hasValueMetrics()).isTrue();
diff --git a/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java b/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java
new file mode 100644
index 0000000..8e563b9
--- /dev/null
+++ b/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java
@@ -0,0 +1,44 @@
+package android.cts.statsd.restricted;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
+
+/**
+ * Tests Suite for restricted stats permissions.
+ */
+public class ReadRestrictedStatsPermissionTest extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testReadRestrictedStatsPermission() throws Exception {
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                ".RestrictedPermissionTests", "testReadRestrictedStatsPermission");
+    }
+}
diff --git a/tests/src/android/cts/statsd/subscriber/ShellSubscriberTest.java b/tests/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
index d3c142b..a79b2b0 100644
--- a/tests/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
+++ b/tests/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
@@ -16,12 +16,11 @@
 package android.cts.statsd.subscriber;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import com.android.compatibility.common.util.CpuFeatures;
 import com.android.internal.os.StatsdConfigProto;
+import com.android.os.AtomsProto;
 import com.android.os.AtomsProto.Atom;
-import com.android.os.AtomsProto.SystemUptime;
 import com.android.os.ShellConfig;
 import com.android.os.statsd.ShellDataProto;
 import com.android.tradefed.device.CollectingByteOutputReceiver;
@@ -29,6 +28,8 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil;
 import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.RunUtil;
+
 import com.google.common.io.Files;
 import com.google.protobuf.InvalidProtocolBufferException;
 
@@ -37,18 +38,20 @@
 import java.nio.ByteOrder;
 import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
-import android.cts.statsd.atom.AtomTestCase;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
 
 /**
  * Statsd shell data subscription test.
  */
-public class ShellSubscriberTest extends AtomTestCase {
+public class ShellSubscriberTest extends DeviceTestCase {
     private int sizetBytes;
 
     public class ShellSubscriptionThread extends Thread {
         String cmd;
         CollectingByteOutputReceiver receiver;
         int maxTimeoutForCommandSec;
+
         public ShellSubscriptionThread(
                 String cmd,
                 CollectingByteOutputReceiver receiver,
@@ -57,7 +60,8 @@
             this.receiver = receiver;
             this.maxTimeoutForCommandSec = maxTimeoutForCommandSec;
         }
-        public void run () {
+
+        public void run() {
             try {
                 getDevice().executeShellCommand(cmd, receiver, maxTimeoutForCommandSec,
                         /*maxTimeToOutputShellResponse=*/maxTimeoutForCommandSec, TimeUnit.SECONDS,
@@ -124,27 +128,28 @@
 
             String cmd = "cat " + remotePath + " |  cmd stats data-subscribe " + timeout;
             String firstSubCmd =
-                        "cat " + remotePath + " |  cmd stats data-subscribe " + firstSubTimeout;
+                    "cat " + remotePath + " |  cmd stats data-subscribe " + firstSubTimeout;
 
             for (int i = 0; i < maxSubs; i++) {
                 // Run data-subscribe on a thread
                 receivers[i] = new CollectingByteOutputReceiver();
                 if (i == 0) {
                     shellThreads[i] =
-                        new ShellSubscriptionThread(firstSubCmd, receivers[i], firstSubTimeout);
+                            new ShellSubscriptionThread(firstSubCmd, receivers[i], firstSubTimeout);
                 } else {
                     shellThreads[i] =
-                        new ShellSubscriptionThread(cmd, receivers[i], timeout);
+                            new ShellSubscriptionThread(cmd, receivers[i], timeout);
                 }
                 shellThreads[i].start();
                 LogUtil.CLog.d("Starting new shell subscription.");
             }
             // Sleep 2 seconds to make sure all subscription clients are initialized before
             // first pushed event
-            Thread.sleep(2000);
+            RunUtil.getDefault().sleep(2000);
 
             // Pushed event. arbitrary label = 1
-            doAppBreadcrumbReported(1);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AtomsProto.AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 1);
 
             // Make sure the last 19 threads die before moving to the next step.
             // First subscription is still active due to its longer timeout that is used keep
@@ -163,26 +168,27 @@
                 // Run data-subscribe on a thread
                 receivers[i] = new CollectingByteOutputReceiver();
                 shellThreads[i] =
-                    new ShellSubscriptionThread(cmd, receivers[i], timeout);
+                        new ShellSubscriptionThread(cmd, receivers[i], timeout);
                 shellThreads[i].start();
                 LogUtil.CLog.d("Starting new shell subscription.");
             }
             // Sleep 2 seconds to make sure all subscription clients are initialized before
             // pushed event
-            Thread.sleep(2000);
+            RunUtil.getDefault().sleep(2000);
 
             // ShellSubscriber only allows 20 subscriptions at a time. This is the 21st which will
             // be ignored
             receivers[maxSubs] = new CollectingByteOutputReceiver();
             shellThreads[maxSubs] =
-                new ShellSubscriptionThread(cmd, receivers[maxSubs], timeout);
+                    new ShellSubscriptionThread(cmd, receivers[maxSubs], timeout);
             shellThreads[maxSubs].start();
 
             // Sleep 1 seconds to ensure that the 21st subscription is rejected
-            Thread.sleep(1000);
+            RunUtil.getDefault().sleep(1000);
 
             // Pushed event. arbitrary label = 1
-            doAppBreadcrumbReported(1);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AtomsProto.AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 1);
 
             // Make sure all the threads die before moving to the next step
             for (int i = 0; i <= maxSubs; i++) {
@@ -219,7 +225,7 @@
     private ShellConfig.ShellSubscription createConfig() {
         return ShellConfig.ShellSubscription.newBuilder()
                 .addPushed((StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER))
                         .build()).build();
     }
 
@@ -260,15 +266,16 @@
             String cmd = "cat " + remotePath + " |  cmd stats data-subscribe " + timeout;
             // Run data-subscribe on a thread
             ShellSubscriptionThread shellThread =
-                                    new ShellSubscriptionThread(cmd, receiver, timeout);
+                    new ShellSubscriptionThread(cmd, receiver, timeout);
             shellThread.start();
             LogUtil.CLog.d("Starting new shell subscription.");
 
             // Sleep a second to make sure subscription is initiated
-            Thread.sleep(1000);
+            RunUtil.getDefault().sleep(1000);
 
             // Pushed event. arbitrary label = 1
-            doAppBreadcrumbReported(1);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                    AtomsProto.AppBreadcrumbReported.State.UNSPECIFIED.getNumber(), 1);
             // Wait for thread to die before returning
             shellThread.join();
             // Remove config from device if not already deleted
@@ -319,7 +326,7 @@
             assertThat(data.getAtom(0).hasAppBreadcrumbReported()).isTrue();
             assertThat(data.getAtom(0).getAppBreadcrumbReported().getLabel()).isEqualTo(1);
             assertThat(data.getAtom(0).getAppBreadcrumbReported().getState().getNumber())
-                       .isEqualTo(1);
+                    .isEqualTo(1);
             atomCount++;
             startIndex += sizetBytes + dataLength;
         }
diff --git a/tests/src/android/cts/statsd/uidmap/UidMapTests.java b/tests/src/android/cts/statsd/uidmap/UidMapTests.java
index 4ceefa7..595a987 100644
--- a/tests/src/android/cts/statsd/uidmap/UidMapTests.java
+++ b/tests/src/android/cts/statsd/uidmap/UidMapTests.java
@@ -17,7 +17,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.internal.os.StatsdConfigProto;
 import com.android.os.AtomsProto;
@@ -25,18 +30,54 @@
 import com.android.os.StatsLog.ConfigMetricsReport;
 import com.android.os.StatsLog.UidMapping;
 import com.android.os.StatsLog.UidMapping.PackageInfoSnapshot;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
-import java.util.List;
+import com.google.protobuf.ExtensionRegistry;
 
-public class UidMapTests extends DeviceAtomTestCase {
+public class UidMapTests extends DeviceTestCase implements IBuildReceiver {
+
+    private static final long DEVICE_SIDE_TEST_PKG_HASH =
+            Long.parseUnsignedLong("15694052924544098582");
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     // Tests that every report has at least one snapshot.
     public void testUidSnapshotIncluded() throws Exception {
         // There should be at least the test app installed during the test setup.
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
 
-        ConfigMetricsReportList reports = getReportList();
+        ConfigMetricsReportList reports = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         for (ConfigMetricsReport report : reports.getReportsList()) {
@@ -64,21 +105,24 @@
 
     // Tests that delta event included during app installation.
     public void testChangeFromInstallation() throws Exception {
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
+        getDevice().uninstallPackage(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
         // Install the package after the config is sent to statsd. The uid map is not guaranteed to
         // be updated if there's no config in statsd.
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
         final String result = getDevice().installPackage(
-                buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), false, true);
+                buildHelper.getTestFile(MetricsUtils.DEVICE_SIDE_TEST_APK), false, true);
 
-        Thread.sleep(WAIT_TIME_LONG);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
 
-        ConfigMetricsReportList reports = getReportList();
+        ConfigMetricsReportList reports = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         boolean found = false;
-        int uid = getUid();
+        int uid = DeviceUtils.getAppUid(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         for (ConfigMetricsReport report : reports.getReportsList()) {
             LogUtil.CLog.d("Got the following report: \n" + report.toString());
             if (hasMatchingChange(report.getUidMap(), uid, false)) {
@@ -91,18 +135,23 @@
     // We check that a re-installation gives a change event (similar to an app upgrade).
     public void testChangeFromReinstall() throws Exception {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), false, true);
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
+        getDevice().installPackage(buildHelper.getTestFile(MetricsUtils.DEVICE_SIDE_TEST_APK),
+                false, true);
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
         // Now enable re-installation.
-        getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), true, true);
+        getDevice().installPackage(buildHelper.getTestFile(MetricsUtils.DEVICE_SIDE_TEST_APK), true,
+                true);
 
-        Thread.sleep(WAIT_TIME_LONG);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
 
-        ConfigMetricsReportList reports = getReportList();
+        ConfigMetricsReportList reports = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         boolean found = false;
-        int uid = getUid();
+        int uid = DeviceUtils.getAppUid(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         for (ConfigMetricsReport report : reports.getReportsList()) {
             LogUtil.CLog.d("Got the following report: \n" + report.toString());
             if (hasMatchingChange(report.getUidMap(), uid, false)) {
@@ -114,14 +163,19 @@
 
     public void testChangeFromUninstall() throws Exception {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        getDevice().installPackage(buildHelper.getTestFile(DEVICE_SIDE_TEST_APK), true, true);
-        createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
-        int uid = getUid();
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        getDevice().installPackage(buildHelper.getTestFile(MetricsUtils.DEVICE_SIDE_TEST_APK), true,
+                true);
 
-        Thread.sleep(WAIT_TIME_LONG);
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
+        int uid = DeviceUtils.getAppUid(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        getDevice().uninstallPackage(MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
 
-        ConfigMetricsReportList reports = getReportList();
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        ConfigMetricsReportList reports = ReportUtils.getReportList(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         boolean found = false;
diff --git a/tests/src/android/cts/statsd/validation/DirectoryValidationTest.java b/tests/src/android/cts/statsd/validation/DirectoryValidationTest.java
index 37ded0b..4fa5e2a 100644
--- a/tests/src/android/cts/statsd/validation/DirectoryValidationTest.java
+++ b/tests/src/android/cts/statsd/validation/DirectoryValidationTest.java
@@ -1,34 +1,64 @@
 package android.cts.statsd.validation;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
 /**
  * Tests Suite for directories used by Statsd.
  */
-public class DirectoryValidationTest extends DeviceAtomTestCase {
+public class DirectoryValidationTest extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     public void testStatsActiveMetricDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".DirectoryTests", "testStatsActiveMetricDirectoryExists");
     }
 
     public void testStatsDataDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".DirectoryTests", "testStatsDataDirectoryExists");
     }
 
     public void testStatsMetadataDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".DirectoryTests", "testStatsMetadataDirectoryExists");
     }
 
     public void testStatsServiceDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".DirectoryTests", "testStatsServiceDirectoryExists");
     }
 
     public void testTrainInfoDirectoryExists() throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".DirectoryTests", "testTrainInfoDirectoryExists");
     }
 }
diff --git a/tests/src/android/cts/statsd/validation/ProcStatsValidationTests.java b/tests/src/android/cts/statsd/validation/ProcStatsValidationTests.java
index 1ca4c5c..6b61497 100644
--- a/tests/src/android/cts/statsd/validation/ProcStatsValidationTests.java
+++ b/tests/src/android/cts/statsd/validation/ProcStatsValidationTests.java
@@ -18,20 +18,22 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.cts.statsd.atom.ProcStateTestCase;
-import android.service.procstats.ProcessState;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 import android.service.procstats.AggregatedProcessState;
 
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.ProcessStateAggregated;
-import com.android.os.AtomsProto.ProcessStatsPackageProto;
 import com.android.os.AtomsProto.ProcessStatsProto;
 import com.android.os.AtomsProto.ProcessStatsStateProto;
-import com.android.os.StatsLog.DimensionsValue;
-import com.android.os.StatsLog.ValueBucketInfo;
-import com.android.os.StatsLog.ValueMetricData;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.util.RunUtil;
 
 import java.util.List;
 
@@ -42,54 +44,61 @@
 
     private static final String TAG = "Statsd.ProcStatsValidationTests";
 
+    private static final String sBackgroundServiceName = "StatsdCtsBackgroundService";
+
     private static final int EXTRA_WAIT_TIME_MS = 1_000; // as buffer when proc state changing.
 
+    private static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
+
     private void toggleScreenAndSleep(final long duration) throws Exception {
         final long half = duration >> 1;
-        Thread.sleep(half);
-        turnScreenOff();
-        Thread.sleep(half);
-        turnScreenOn();
+        RunUtil.getDefault().sleep(half);
+        DeviceUtils.turnScreenOff(getDevice());
+        RunUtil.getDefault().sleep(half);
+        DeviceUtils.turnScreenOn(getDevice());
     }
 
     public void testProcessStateByPulling() throws Exception {
         startProcStatsTesting();
         clearProcStats();
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
         // foreground service
         executeForegroundService();
-        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
+        RunUtil.getDefault().sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
         // background
-        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
-        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
+        DeviceUtils.executeBackgroundService(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                sBackgroundServiceName, ACTION_BACKGROUND_SLEEP);
+        RunUtil.getDefault().sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
         // top
         executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
-        Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
+        RunUtil.getDefault().sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
         // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
-        executeBackgroundService(ACTION_END_IMMEDIATELY);
+        DeviceUtils.executeBackgroundService(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
+                sBackgroundServiceName, ACTION_END_IMMEDIATELY);
         final int cacheTime = 2_000; // process should be in cached state for up to this long
-        Thread.sleep(cacheTime);
+        RunUtil.getDefault().sleep(cacheTime);
         // foreground
         // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
         executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
-        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
+        RunUtil.getDefault().sleep(EXTRA_WAIT_TIME_MS + 5_000);
 
-        Thread.sleep(60_000);
-        uninstallPackage();
+        RunUtil.getDefault().sleep(60_000);
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         stopProcStatsTesting();
         commitProcStatsToDisk();
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
         final String fileName = "PROCSTATSQ_PULL.pbtxt";
-        StatsdConfig config = createValidationUtil().getConfig(fileName);
+        StatsdConfig config = ValidationTestUtil.getConfig(fileName, mCtsBuild);
         LogUtil.CLog.d("Updating the following config:\n" + config.toString());
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT + 5_000);
+        ConfigUtils.uploadConfig(getDevice(), config.toBuilder());
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice(),
+                AtomsProto.AppBreadcrumbReported.State.START.getNumber(), 1);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT + 5_000);
 
-        List<Atom> statsdData = getGaugeMetricDataList();
+        List<Atom> statsdData = ReportUtils.getGaugeMetricAtoms(getDevice());
 
         List<android.service.procstats.ProcessStatsProto> processStatsProtoList
                 = getAllProcStatsProtoForStatsd();
@@ -101,7 +110,8 @@
         String statsdPkgName = "com.android.server.cts.device.statsd";
         long rssAvgStatsd = 0;
         for (Atom d : statsdData) {
-            for (ProcessStatsProto proc : d.getProcStats().getProcStatsSection().getProcessStatsList()) {
+            for (ProcessStatsProto proc :
+                    d.getProcStats().getProcStatsSection().getProcessStatsList()) {
                 if (proc.getProcess().equals(statsdPkgName)) {
                     LogUtil.CLog.d("Got proto from statsd:");
                     LogUtil.CLog.d(proc.toString());
@@ -116,7 +126,7 @@
         }
 
         long rssAvgProcstats = 0;
-        for (android.service.procstats.ProcessStatsProto process: processStatsProtoList) {
+        for (android.service.procstats.ProcessStatsProto process : processStatsProtoList) {
             if (process.getProcess().equals(statsdPkgName)) {
                 LogUtil.CLog.d("Got proto from procstats dumpsys:");
                 LogUtil.CLog.d(process.toString());
@@ -139,111 +149,112 @@
          * Temporarily disable this test as the proc stats data being pulled into the statsd
          * doesn't include the pkg part now.
          *
-        startProcStatsTesting();
-        clearProcStats();
-        Thread.sleep(WAIT_TIME_SHORT);
+         startProcStatsTesting();
+         clearProcStats();
+         RunUtil.getDefault().sleep(WAIT_TIME_SHORT);
 
-        // foreground service
-        executeForegroundService();
-        Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
-        // background
-        executeBackgroundService(ACTION_BACKGROUND_SLEEP);
-        Thread.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
-        // top
-        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
-        Thread.sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
-        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
-        executeBackgroundService(ACTION_END_IMMEDIATELY);
-        final int cacheTime = 2_000; // process should be in cached state for up to this long
-        Thread.sleep(cacheTime);
-        // foreground
-        // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
-        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
-        Thread.sleep(EXTRA_WAIT_TIME_MS + 5_000);
+         // foreground service
+         executeForegroundService();
+         RunUtil.getDefault().sleep(SLEEP_OF_FOREGROUND_SERVICE + EXTRA_WAIT_TIME_MS);
+         // background
+         executeBackgroundService(ACTION_BACKGROUND_SLEEP);
+         RunUtil.getDefault().sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP + EXTRA_WAIT_TIME_MS);
+         // top
+         executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+         RunUtil.getDefault().sleep(SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS);
+         // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
+         executeBackgroundService(ACTION_END_IMMEDIATELY);
+         final int cacheTime = 2_000; // process should be in cached state for up to this long
+         RunUtil.getDefault().sleep(cacheTime);
+         // foreground
+         // overlay should take 2 sec to appear. So this makes it 4 sec in TOP
+         executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+         RunUtil.getDefault().sleep(EXTRA_WAIT_TIME_MS + 5_000);
 
-        Thread.sleep(60_000);
-        uninstallPackage();
-        stopProcStatsTesting();
-        commitProcStatsToDisk();
-        Thread.sleep(WAIT_TIME_SHORT);
+         RunUtil.getDefault().sleep(60_000);
+         uninstallPackage();
+         stopProcStatsTesting();
+         commitProcStatsToDisk();
+         RunUtil.getDefault().sleep(WAIT_TIME_SHORT);
 
-        final String fileName = "PROCSTATSQ_PULL_PKG_PROC.pbtxt";
-        StatsdConfig config = createValidationUtil().getConfig(fileName);
-        LogUtil.CLog.d("Updating the following config:\n" + config.toString());
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_SHORT);
+         final String fileName = "PROCSTATSQ_PULL_PKG_PROC.pbtxt";
+         StatsdConfig config = createValidationUtil().getConfig(fileName);
+         LogUtil.CLog.d("Updating the following config:\n" + config.toString());
+         uploadConfig(config);
+         RunUtil.getDefault().sleep(WAIT_TIME_SHORT);
+         setAppBreadcrumbPredicate();
+         RunUtil.getDefault().sleep(WAIT_TIME_SHORT);
 
-        List<Atom> statsdData = getGaugeMetricDataList();
-        assertThat(statsdData).isNotEmpty();
-        assertThat(
-                statsdData.get(0).getProcStatsPkgProc().getProcStatsSection()
-                        .getProcessStatsList()
-        ).isNotEmpty();
+         List<Atom> statsdData = getGaugeMetricDataList();
+         assertThat(statsdData).isNotEmpty();
+         assertThat(
+         statsdData.get(0).getProcStatsPkgProc().getProcStatsSection()
+         .getProcessStatsList()
+         ).isNotEmpty();
 
-        // We pull directly from ProcessStatsService, so not necessary to compare every field.
-        // Make sure that 1. both capture statsd package 2. spot check some values are reasonable
-        LogUtil.CLog.d("======================");
+         // We pull directly from ProcessStatsService, so not necessary to compare every field.
+         // Make sure that 1. both capture statsd package 2. spot check some values are reasonable
+         LogUtil.CLog.d("======================");
 
-        String statsdPkgName = "com.android.server.cts.device.statsd";
-        long rssAvgStatsd = 0;
-        long durationStatsd = 0;
-        for (Atom d : statsdData) {
-            for (ProcessStatsPackageProto pkg : d.getProcStatsPkgProc().getProcStatsSection().getPackageStatsList()) {
-                if (pkg.getPackage().equals(statsdPkgName)) {
-                    LogUtil.CLog.d("Got proto from statsd:");
-                    LogUtil.CLog.d(pkg.toString());
-                    for (ProcessStatsProto process : pkg.getProcessStatsList()) {
-                        for (ProcessStatsStateProto state : process.getStatesList()) {
-                            if (state.getProcessState()
-                                    == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                                durationStatsd = state.getDurationMillis();
-                                rssAvgStatsd = state.getRss().getAverage();
-                            }
-                        }
-                    }
-                }
-                assertThat(pkg.getServiceStatsCount()).isEqualTo(0L);
-                assertThat(pkg.getAssociationStatsCount()).isEqualTo(0L);
-            }
-        }
+         String statsdPkgName = "com.android.server.cts.device.statsd";
+         long rssAvgStatsd = 0;
+         long durationStatsd = 0;
+         for (Atom d : statsdData) {
+         for (ProcessStatsPackageProto pkg : d.getProcStatsPkgProc().getProcStatsSection()
+         .getPackageStatsList()) {
+         if (pkg.getPackage().equals(statsdPkgName)) {
+         LogUtil.CLog.d("Got proto from statsd:");
+         LogUtil.CLog.d(pkg.toString());
+         for (ProcessStatsProto process : pkg.getProcessStatsList()) {
+         for (ProcessStatsStateProto state : process.getStatesList()) {
+         if (state.getProcessState()
+         == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+         durationStatsd = state.getDurationMillis();
+         rssAvgStatsd = state.getRss().getAverage();
+         }
+         }
+         }
+         }
+         assertThat(pkg.getServiceStatsCount()).isEqualTo(0L);
+         assertThat(pkg.getAssociationStatsCount()).isEqualTo(0L);
+         }
+         }
 
-        LogUtil.CLog.d("avg rss from statsd is " + rssAvgStatsd);
+         LogUtil.CLog.d("avg rss from statsd is " + rssAvgStatsd);
 
-        List<ProcessStatsPackageProto> processStatsPackageProtoList = getAllProcStatsProto();
+         List<ProcessStatsPackageProto> processStatsPackageProtoList = getAllProcStatsProto();
 
-        long pssAvgProcstats = 0;
-        long ussAvgProcstats = 0;
-        long rssAvgProcstats = 0;
-        long durationProcstats = 0;
-        int serviceStatsCount = 0;
-        int associationStatsCount = 0;
-        for (ProcessStatsPackageProto pkg : processStatsPackageProtoList) {
-            if (pkg.getPackage().equals(statsdPkgName)) {
-                LogUtil.CLog.d("Got proto from procstats dumpsys:");
-                LogUtil.CLog.d(pkg.toString());
-                for (ProcessStatsProto process : pkg.getProcessStatsList()) {
-                    for (ProcessStatsStateProto state : process.getStatesList()) {
-                        if (state.getProcessState()
-                                == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                            durationProcstats = state.getDurationMillis();
-                            pssAvgProcstats = state.getPss().getAverage();
-                            ussAvgProcstats = state.getUss().getAverage();
-                            rssAvgProcstats = state.getRss().getAverage();
-                        }
-                    }
-                }
-            }
-            serviceStatsCount += pkg.getServiceStatsCount();
-            associationStatsCount += pkg.getAssociationStatsCount();
-        }
-        assertThat(serviceStatsCount).isGreaterThan(0);
-        assertThat(associationStatsCount).isGreaterThan(0);
+         long pssAvgProcstats = 0;
+         long ussAvgProcstats = 0;
+         long rssAvgProcstats = 0;
+         long durationProcstats = 0;
+         int serviceStatsCount = 0;
+         int associationStatsCount = 0;
+         for (ProcessStatsPackageProto pkg : processStatsPackageProtoList) {
+         if (pkg.getPackage().equals(statsdPkgName)) {
+         LogUtil.CLog.d("Got proto from procstats dumpsys:");
+         LogUtil.CLog.d(pkg.toString());
+         for (ProcessStatsProto process : pkg.getProcessStatsList()) {
+         for (ProcessStatsStateProto state : process.getStatesList()) {
+         if (state.getProcessState()
+         == ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+         durationProcstats = state.getDurationMillis();
+         pssAvgProcstats = state.getPss().getAverage();
+         ussAvgProcstats = state.getUss().getAverage();
+         rssAvgProcstats = state.getRss().getAverage();
+         }
+         }
+         }
+         }
+         serviceStatsCount += pkg.getServiceStatsCount();
+         associationStatsCount += pkg.getAssociationStatsCount();
+         }
+         assertThat(serviceStatsCount).isGreaterThan(0);
+         assertThat(associationStatsCount).isGreaterThan(0);
 
-        LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
-        assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
-        */
+         LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
+         assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
+         */
     }
 
     private boolean isPssProfilingDisabled() throws Exception {
@@ -254,4 +265,45 @@
         final String dumpsys = device.executeShellCommand("dumpsys activity settings");
         return (dumpsys.contains(stringToCompare));
     }
+
+    protected void clearProcStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --clear");
+    }
+
+    private void startProcStatsTesting() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --start-testing");
+    }
+
+    private void stopProcStatsTesting() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --stop-testing");
+    }
+
+    private void commitProcStatsToDisk() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --commit");
+    }
+
+    /*
+     * Get all processes' procstats statsd data in proto
+     */
+    protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd()
+            throws Exception {
+        try {
+            android.service.procstats.ProcessStatsSectionProto sectionProto = MetricsUtils.getDump(
+                    getDevice(),
+                    android.service.procstats.ProcessStatsSectionProto.parser(),
+                    String.join(" ", DUMP_PROCSTATS_CMD,
+                            "--statsd"));
+            List<android.service.procstats.ProcessStatsProto> processStatsProtoList
+                    = sectionProto.getProcessStatsList();
+            LogUtil.CLog.d("Got procstats:\n ");
+            for (android.service.procstats.ProcessStatsProto processStatsProto
+                    : processStatsProtoList) {
+                LogUtil.CLog.d(processStatsProto.toString());
+            }
+            return processStatsProtoList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump procstats proto");
+            throw (e);
+        }
+    }
 }
diff --git a/tests/src/android/cts/statsd/validation/StatsFrameworkInitializerTest.java b/tests/src/android/cts/statsd/validation/StatsFrameworkInitializerTest.java
index 0ea332e..2a67748 100644
--- a/tests/src/android/cts/statsd/validation/StatsFrameworkInitializerTest.java
+++ b/tests/src/android/cts/statsd/validation/StatsFrameworkInitializerTest.java
@@ -16,13 +16,43 @@
 
 package android.cts.statsd.validation;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import static com.google.common.truth.Truth.assertThat;
 
-public class StatsFrameworkInitializerTest extends DeviceAtomTestCase {
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
+
+public class StatsFrameworkInitializerTest extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     public void testStatsFrameworkInitializer_failsWhenCalledOutsideOfSystemServiceRegistry()
             throws Exception {
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE,
                 ".StatsFrameworkInitializerTests", "testRegisterServiceWrappers_expectFail");
     }
 
diff --git a/tests/src/android/cts/statsd/validation/ValidationTestUtil.java b/tests/src/android/cts/statsd/validation/ValidationTestUtil.java
index d3e5bad..cb3a41e 100644
--- a/tests/src/android/cts/statsd/validation/ValidationTestUtil.java
+++ b/tests/src/android/cts/statsd/validation/ValidationTestUtil.java
@@ -15,11 +15,10 @@
  */
 package android.cts.statsd.validation;
 
-import android.cts.statsd.atom.BaseTestCase;
-
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.util.FileUtil;
 
 import com.google.protobuf.TextFormat;
@@ -28,14 +27,14 @@
 import java.io.File;
 import java.io.IOException;
 
-public class ValidationTestUtil extends BaseTestCase {
+public class ValidationTestUtil {
 
     private static final String TAG = "Statsd.ValidationTestUtil";
 
-    public StatsdConfig getConfig(String fileName) throws IOException {
+    public static StatsdConfig getConfig(String fileName, IBuildInfo ctsBuild) throws IOException {
         try {
             // TODO: Ideally, we should use real metrics that are also pushed to the fleet.
-            File configFile = getBuildHelper().getTestFile(fileName);
+            File configFile = getBuildHelper(ctsBuild).getTestFile(fileName);
             String configStr = FileUtil.readStringFromFile(configFile);
             StatsdConfig.Builder builder = StatsdConfig.newBuilder();
             TextFormat.merge(configStr, builder);
@@ -47,4 +46,8 @@
         }
         return null;
     }
+
+    private static CompatibilityBuildHelper getBuildHelper(IBuildInfo ctsBuild) {
+        return new CompatibilityBuildHelper(ctsBuild);
+    }
 }
diff --git a/tests/src/android/cts/statsd/validation/ValidationTests.java b/tests/src/android/cts/statsd/validation/ValidationTests.java
index 3add66d..a3f0109 100644
--- a/tests/src/android/cts/statsd/validation/ValidationTests.java
+++ b/tests/src/android/cts/statsd/validation/ValidationTests.java
@@ -18,7 +18,11 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.cts.statsd.atom.DeviceAtomTestCase;
+import android.cts.statsd.metric.MetricsUtils;
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
 import android.os.BatteryPluggedStateEnum;
 import android.os.BatteryStatsProto;
 import android.os.UidProto;
@@ -47,9 +51,14 @@
 import com.android.os.StatsLog.DurationMetricData;
 import com.android.os.StatsLog.EventMetricData;
 import com.android.os.StatsLog.StatsLogReport;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
 import com.google.common.collect.Range;
+import com.google.protobuf.ExtensionRegistry;
 
 import java.util.Arrays;
 import java.util.HashMap;
@@ -60,36 +69,53 @@
 /**
  * Side-by-side comparison between statsd and batterystats.
  */
-public class ValidationTests extends DeviceAtomTestCase {
+public class ValidationTests extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_APK,
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, mCtsBuild);
+        RunUtil.getDefault().sleep(1000);
+        DeviceUtils.turnBatteryStatsAutoResetOff(
+                getDevice()); // Turn off Battery Stats auto resetting
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
+        DeviceUtils.resetBatteryStatus(getDevice());
+        DeviceUtils.turnScreenOn(getDevice());
+        DeviceUtils.turnBatteryStatsAutoResetOn(getDevice());
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
 
     private static final String TAG = "Statsd.ValidationTests";
     private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
     private static final boolean ENABLE_LOAD_TEST = false;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        turnBatteryStatsAutoResetOff(); // Turn off Battery Stats auto resetting
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        resetBatteryStatus(); // Undo any unplugDevice().
-        turnScreenOn(); // Reset screen to on state
-        turnBatteryStatsAutoResetOn(); // Turn Battery Stats auto resetting back on
-        super.tearDown();
-    }
-
     public void testPartialWakelock() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
         resetBatteryStats();
-        unplugDevice();
-        flushBatteryStatsHandlers();
+        DeviceUtils.unplugDevice(getDevice());
+        DeviceUtils.flushBatteryStatsHandlers(getDevice());
         // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
         // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
-        String aodState = getAodState();
-        setAodState("0");
-        turnScreenOff();
+        String aodState = DeviceUtils.getAodState(getDevice());
+        DeviceUtils.setAodState(getDevice(), "0");
+        DeviceUtils.turnScreenOff(getDevice());
 
         final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
         Set<Integer> wakelockOn = new HashSet<>(Arrays.asList(
@@ -105,15 +131,17 @@
         // Add state sets to the list in order.
         List<Set<Integer>> stateSet = Arrays.asList(wakelockOn, wakelockOff);
 
-        createAndUploadConfig(atomTag, true);  // True: uses attribution.
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, atomTag, true);  // True: uses attribution.
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, ".AtomTests",
+                "testWakelockState");
 
         // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = getEventMetricDataList();
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         //=================== verify that statsd is correct ===============//
         // Assert that the events happened in the expected order.
-        assertStatesOccurred(stateSet, data, WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurred(stateSet, data,
                 atom -> atom.getWakelockStateChanged().getState().getNumber());
 
         for (EventMetricData event : data) {
@@ -126,28 +154,30 @@
 
     @RestrictedBuildTest
     public void testPartialWakelockDuration() throws Exception {
-        if (!hasFeature(FEATURE_AUTOMOTIVE, false)) return;
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
 
         // getUid() needs shell command via ADB. turnScreenOff() sometimes let system go to suspend.
         // ADB disconnection causes failure of getUid(). Move up here before turnScreenOff().
-        final int EXPECTED_UID = getUid();
+        final int EXPECTED_UID = DeviceUtils.getAppUid(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
 
-        turnScreenOn(); // To ensure that the ScreenOff later gets logged.
+        DeviceUtils.turnScreenOn(getDevice()); // To ensure that the ScreenOff later gets logged.
         // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
         // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
-        String aodState = getAodState();
-        setAodState("0");
+        String aodState = DeviceUtils.getAodState(getDevice());
+        DeviceUtils.setAodState(getDevice(), "0");
         uploadWakelockDurationBatteryStatsConfig(TimeUnit.CTS);
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
         resetBatteryStats();
-        unplugDevice();
-        turnScreenOff();
-        flushBatteryStatsHandlers();
+        DeviceUtils.unplugDevice(getDevice());
+        DeviceUtils.turnScreenOff(getDevice());
+        DeviceUtils.flushBatteryStatsHandlers(getDevice());
 
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockState");
-        Thread.sleep(WAIT_TIME_LONG); // Make sure the one second bucket has ended.
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, ".AtomTests",
+                "testWakelockState");
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); // Make sure the one second bucket has ended.
 
 
         final String EXPECTED_TAG = "StatsdPartialWakelock";
@@ -169,33 +199,35 @@
                 EXPECTED_UID, EXPECTED_TAG
         ).that(statsdDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
 
-        setAodState(aodState); // restores AOD to initial state.
+        DeviceUtils.setAodState(getDevice(), aodState); // restores AOD to initial state.
     }
 
     public void testPartialWakelockLoad() throws Exception {
         if (!ENABLE_LOAD_TEST) return;
-        turnScreenOn(); // To ensure that the ScreenOff later gets logged.
+        DeviceUtils.turnScreenOn(getDevice()); // To ensure that the ScreenOff later gets logged.
         uploadWakelockDurationBatteryStatsConfig(TimeUnit.CTS);
-        Thread.sleep(WAIT_TIME_SHORT);
+        RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_SHORT);
         resetBatteryStats();
-        unplugDevice();
-        turnScreenOff();
+        DeviceUtils.unplugDevice(getDevice());
+        DeviceUtils.turnScreenOff(getDevice());
 
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testWakelockLoad");
+        DeviceUtils.runDeviceTests(getDevice(), MetricsUtils.DEVICE_SIDE_TEST_PACKAGE, ".AtomTests",
+                "testWakelockLoad");
         // Give time for stuck wakelocks to increase duration.
-        Thread.sleep(10_000);
+        RunUtil.getDefault().sleep(10_000);
 
 
         final String EXPECTED_TAG = "StatsdPartialWakelock";
-        final int EXPECTED_UID = getUid();
+        final int EXPECTED_UID = DeviceUtils.getAppUid(getDevice(),
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         final int NUM_THREADS = 16;
         final int NUM_COUNT_PER_THREAD = 1000;
         final int MAX_DURATION_MS = 15_000;
         final int MIN_DURATION_MS = 1_000;
 
 
-        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-        HashMap<Integer, HashMap<Long, Long>> statsdWakelockData = getStatsdWakelockData();
+//        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
+//        HashMap<Integer, HashMap<Long, Long>> statsdWakelockData = getStatsdWakelockData();
 
         // TODO: this fails because we only have the hashes of the wakelock tags in statsd.
         // If we want to run this test, we need to fix this.
@@ -256,7 +288,8 @@
     // TODO: Refactor these into some utils class.
 
     public HashMap<Integer, HashMap<Long, Long>> getStatsdWakelockData() throws Exception {
-        StatsLogReport report = getStatsLogReport();
+        StatsLogReport report = ReportUtils.getStatsLogReport(getDevice(),
+                ExtensionRegistry.getEmptyRegistry());
         CLog.d("Received the following stats log report: \n" + report.toString());
 
         // Stores total duration of each wakelock across buckets.
@@ -607,13 +640,14 @@
                         .addPredicate(screenIsOffId)
                         .addPredicate(deviceIsUnpluggedId));
 
-        StatsdConfig.Builder builder = createConfigBuilder();
+        StatsdConfig.Builder builder = ConfigUtils.createConfigBuilder(
+                MetricsUtils.DEVICE_SIDE_TEST_PACKAGE);
         builder.addDurationMetric(DurationMetric.newBuilder()
-                .setId(metricId)
-                .setWhat(partialWakelockIsOnId)
-                .setCondition(screenOffBatteryOnId)
-                .setDimensionsInWhat(dimensions)
-                .setBucket(bucketsize))
+                        .setId(metricId)
+                        .setWhat(partialWakelockIsOnId)
+                        .setCondition(screenOffBatteryOnId)
+                        .setDimensionsInWhat(dimensions)
+                        .setBucket(bucketsize))
                 .addAtomMatcher(wakelockAcquire)
                 .addAtomMatcher(wakelockChangeAcquire)
                 .addAtomMatcher(wakelockRelease)
@@ -639,6 +673,10 @@
                 .addPredicate(screenIsOff)
                 .addPredicate(screenOffBatteryOn);
 
-        uploadConfig(builder);
+        ConfigUtils.uploadConfig(getDevice(), builder);
+    }
+
+    private void resetBatteryStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys batterystats --reset");
     }
 }
diff --git a/tests/utils/tests/Android.bp b/tests/utils/tests/Android.bp
index 88476b1..58063ff 100644
--- a/tests/utils/tests/Android.bp
+++ b/tests/utils/tests/Android.bp
@@ -29,7 +29,7 @@
     manifest: "AndroidManifest.xml",
     static_libs: [
         "androidx.test.rules",
-        "truth-prebuilt",
+        "truth",
         "statsdprotolite",
         "StatsdTestUtils",
     ],
@@ -48,6 +48,10 @@
 genrule {
     name: "statslog-statsdtest-java-gen",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --java $(out) --module statsdtest --javaPackage android.util --javaClass StatsdTestStatsLog",
+    cmd: "$(location stats-log-api-gen) " +
+        "--java $(out) " +
+        "--module statsdtest " +
+        "--javaPackage android.util " +
+        "--javaClass StatsdTestStatsLog",
     out: ["android/util/StatsdTestStatsLog.java"],
 }
diff --git a/tests/utils/tests/AndroidTest.xml b/tests/utils/tests/AndroidTest.xml
new file mode 100644
index 0000000..0438508
--- /dev/null
+++ b/tests/utils/tests/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for StatsdTestUtilsTest.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+    <option name="test-suite-tag" value="mts" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="StatsdTestUtilsTest.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.os.statsd.testutils.test" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
+    </object>
+</configuration>