[automerger skipped] [Cherry-pick] Increase post-reboot sleep am: b96236cf75 am: 2b663bffda am: 1af36ac6f1 -s ours

am skip reason: Merged-In I978e2c37cb65afde3cf4b9d43297c48cb822aedb with SHA-1 8f4fc2b1db is already in history

Original change: https://android-review.googlesource.com/c/platform/packages/modules/StatsD/+/2806054

Change-Id: I0e72aae505daa27c95d05da56af10be669020bd7
Signed-off-by: Automerger Merge Worker <[email protected]>
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/TEST_MAPPING b/TEST_MAPPING
index 73434a3..121ca17 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -27,7 +27,7 @@
       "name" : "statsd_test"
     }
   ],
-  "hwasan-postsubmit" : [
+  "hwasan-presubmit" : [
     {
       "name" : "FrameworkStatsdTest"
     },
diff --git a/aidl/Android.bp b/aidl/Android.bp
index 865c083..c79f0b8 100644
--- a/aidl/Android.bp
+++ b/aidl/Android.bp
@@ -26,11 +26,14 @@
     name: "statsd-aidl",
     unstable: true,
     srcs: [
+        "android/os/StatsSubscriptionCallbackReason.aidl",
+        "android/os/IStatsSubscriptionCallback.aidl",
         "android/os/IPendingIntentRef.aidl",
         "android/os/IPullAtomCallback.aidl",
         "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 b59a97e..ef1e4c6 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.
@@ -131,6 +132,31 @@
     oneway void registerPullAtomCallback(int atomTag, long coolDownMillis, long timeoutMillis,
             in int[] additiveFields, IPullAtomCallback pullerCallback);
 
-    /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */
+    /** 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);
 }
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/IStatsSubscriptionCallback.aidl b/aidl/android/os/IStatsSubscriptionCallback.aidl
new file mode 100644
index 0000000..abc2d35
--- /dev/null
+++ b/aidl/android/os/IStatsSubscriptionCallback.aidl
@@ -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.
+ */
+
+package android.os;
+
+import android.os.StatsSubscriptionCallbackReason;
+
+/**
+  * Binder interface for callback that provides data for a subscription.
+  * {@hide}
+  */
+oneway interface IStatsSubscriptionCallback {
+    /**
+     * Send back subscription data.
+     */
+     void onSubscriptionData(StatsSubscriptionCallbackReason reason, in byte[] subscriptionPayload);
+}
diff --git a/aidl/android/os/IStatsd.aidl b/aidl/android/os/IStatsd.aidl
index 2a0fe69..2cbaf9d 100644
--- a/aidl/android/os/IStatsd.aidl
+++ b/aidl/android/os/IStatsd.aidl
@@ -16,10 +16,12 @@
 
 package android.os;
 
+import android.os.IStatsSubscriptionCallback;
 import android.os.IPendingIntentRef;
 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.
@@ -240,4 +242,64 @@
      * Notifies of properties in statsd_java namespace.
      */
     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.
+     *
+     * IStatsSubscriptionCallback Binder interface will be used to deliver subscription data back to
+     * the subscriber. IStatsSubscriptionCallback also uniquely identifies this subscription - it
+     * should not be reused for another subscription.
+     *
+     * Enforces caller is in the traced_probes selinux domain.
+     */
+    oneway void addSubscription(in byte[] subscriptionConfig,
+            IStatsSubscriptionCallback callback);
+
+    /**
+     * Unsubscribe from a given subscription identified by the IBinder token.
+     *
+     * This will subsequently trigger IStatsSubscriptionCallback with pending data
+     * for this subscription.
+     *
+     * Enforces caller is in the traced_probes selinux domain.
+     */
+    oneway void removeSubscription(IStatsSubscriptionCallback callback);
+
+    /**
+     * Flush data for a subscription.
+     *
+     * This will subsequently trigger IStatsSubscriptionCallback with pending data
+     * for this subscription.
+     *
+     * Enforces caller is in the traced_probes selinux domain.
+     */
+    oneway void flushSubscription(IStatsSubscriptionCallback callback);
 }
diff --git a/aidl/android/os/StatsSubscriptionCallbackReason.aidl b/aidl/android/os/StatsSubscriptionCallbackReason.aidl
new file mode 100644
index 0000000..5e3a945
--- /dev/null
+++ b/aidl/android/os/StatsSubscriptionCallbackReason.aidl
@@ -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.
+ */
+
+package android.os;
+
+
+/**
+ * @hide
+ */
+@Backing(type="int")
+enum StatsSubscriptionCallbackReason {
+    STATSD_INITIATED = 1,
+    FLUSH_REQUESTED = 2,
+    SUBSCRIPTION_ENDED = 3,
+}
diff --git a/framework/Android.bp b/framework/Android.bp
index 634d818..360801b 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -46,15 +46,26 @@
     ],
     visibility: ["//packages/modules/StatsD/framework:__subpackages__"],
 }
+
 java_sdk_library {
     name: "framework-statsd",
     defaults: ["framework-module-defaults"],
     installable: true,
 
+    jarjar_rules: "jarjar-rules.txt",
+
     srcs: [
         ":framework-statsd-sources",
     ],
 
+    libs: [
+        "androidx.annotation_annotation",
+    ],
+
+    static_libs: [
+         "modules-utils-build",
+    ],
+
     permitted_packages: [
         "android.app",
         "android.os",
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/jarjar-rules.txt b/framework/jarjar-rules.txt
new file mode 100644
index 0000000..c78153d
--- /dev/null
+++ b/framework/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.android.modules.utils.** com.android.internal.statsd.@0
\ No newline at end of file
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 92eb416..a0379fd 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 android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
@@ -25,9 +26,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.RemoteException;
 import android.os.StatsFrameworkInitializer;
 import android.util.AndroidException;
@@ -35,8 +39,11 @@
 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;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -95,6 +102,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.
      */
@@ -120,7 +133,7 @@
     /**
      * @hide
      **/
-    @VisibleForTesting public static final long DEFAULT_TIMEOUT_MILLIS = 2_000L; // 2 seconds.
+    @VisibleForTesting public static final long DEFAULT_TIMEOUT_MILLIS = 1_500L; // 1.5 seconds.
 
     /**
      * Constructor for StatsManagerClient.
@@ -363,6 +376,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)}
@@ -721,6 +843,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.
@@ -734,4 +902,18 @@
             super("Failed to connect to statsd: " + reason, e);
         }
     }
+
+    /**
+     * 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);
+        }
+    }
 }
diff --git a/framework/java/android/app/StatsQuery.java b/framework/java/android/app/StatsQuery.java
new file mode 100644
index 0000000..a4a315c
--- /dev/null
+++ b/framework/java/android/app/StatsQuery.java
@@ -0,0 +1,172 @@
+/*
+ * 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;
+
+/**
+ * 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})
+    @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/lib/libstatsgtestmatchers/Android.bp b/lib/libstatsgtestmatchers/Android.bp
new file mode 100644
index 0000000..fe832fa
--- /dev/null
+++ b/lib/libstatsgtestmatchers/Android.bp
@@ -0,0 +1,53 @@
+//
+// 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.
+//
+
+// ==========================================================
+// Native library for custom GoogleTest matchers
+// ==========================================================
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test_library {
+    name: "libstatsgtestmatchers",
+    srcs: [
+        ":libstats_log_protos",
+        ":libstats_subscription_protos",
+    ],
+    export_include_dirs: ["include"],
+    proto: {
+        type: "lite",
+        include_dirs: [
+            "external/protobuf/src",
+        ],
+        static: true,
+    },
+    static_libs: [
+        "libgmock",
+        "libprotobuf-cpp-lite",
+    ],
+    shared: {
+        enabled: false,
+    },
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    visibility: [
+        "//packages/modules/StatsD/lib/libstatspull",
+        "//packages/modules/StatsD/statsd",
+    ],
+}
diff --git a/lib/libstatsgtestmatchers/include/gtest_matchers.h b/lib/libstatsgtestmatchers/include/gtest_matchers.h
new file mode 100644
index 0000000..084346d
--- /dev/null
+++ b/lib/libstatsgtestmatchers/include/gtest_matchers.h
@@ -0,0 +1,231 @@
+// 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <ostream>
+
+#include "frameworks/proto_logging/stats/atoms.pb.h"
+#include "frameworks/proto_logging/stats/attribution_node.pb.h"
+#include "packages/modules/StatsD/statsd/src/shell/shell_data.pb.h"
+#include "packages/modules/StatsD/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using PackageInfo = UidMapping_PackageInfoSnapshot_PackageInfo;
+
+// clang-format off
+
+#define PROPERTY_MATCHER(typename, propname, matcher)                               \
+    testing::Property(#propname, &typename::propname, matcher(expected.propname()))
+
+#define REPEATED_PROPERTY_MATCHER(typename, propname, matcher)                      \
+    testing::Property(#propname, &typename::propname,                               \
+            testing::Pointwise(matcher(), expected.propname()))
+
+#define PROPERTY_EQ(typename, propname) PROPERTY_MATCHER(typename, propname, testing::Eq)
+
+#define REPEATED_PROPERTY_EQ(typename, propname)               \
+    REPEATED_PROPERTY_MATCHER(typename, propname, testing::Eq)
+
+/**
+ * Generate a GoogleTest equality matcher for a custom type specified by typename with the given
+ * properties.
+ *
+ * Example: For the protos:
+ *     message Bar {
+ *         optional int32 aa = 1;
+ *     }
+ *     message Foo {
+ *         optional int32 a = 1;
+ *         repeated float b = 2;
+ *         optional Bar bar = 3;
+ *         repeated Bar repeated_bar = 4;
+ *     }
+ * This will generate equality matchers for them called EqBar() and EqFoo():
+ *      EQ_MATCHER(Bar, PROPERTY_EQ(Bar, aa));
+ *      EQ_MATCHER(Foo,
+ *              PROPERTY_EQ(Foo, a),
+ *              PROPERTY_EQ(Foo, b),
+ *              PROPERTY_MATCHER(Foo, bar, EqBar),
+ *              REPEATED_PROPERTY_MATCHER(Foo, repeated_bar, EqBar)
+ *      );
+ */
+#define EQ_MATCHER(typename, properties...)                                                   \
+    MATCHER(Eq##typename, " ") {                                                              \
+        return testing::Matches(Eq##typename(std::get<1>(arg)))(std::get<0>(arg));            \
+    }                                                                                         \
+    MATCHER_P(Eq##typename, expected, testing::PrintToString(expected)) {                     \
+        return testing::ExplainMatchResult(testing::AllOf(properties), arg, result_listener); \
+    }
+
+#define PROPERTY_PRINT(propname)                                                    \
+    if (obj.has_##propname()) {                                                     \
+        *os << #propname << ": " << testing::PrintToString(obj.propname()) << ", "; \
+    }
+
+#define REPEATED_PROPERTY_PRINT(propname)                                           \
+    if (obj.propname##_size() > 0) {                                                \
+        *os << #propname << ": " << testing::PrintToString(obj.propname()) << ", "; \
+    }
+
+/**
+ * Generates a method that teaches GoogleTest how to print a custom type specified by typename
+ * with the given properties.
+ * The equality matcher generated by EQ_MATCHER for the given typename will use this generated
+ * printer method to print the object when the matcher fails.
+ *
+ * Example: For the protos:
+ *     message Bar {
+ *         optional int32 aa = 1;
+ *     }
+ *     message Foo {
+ *         optional int32 a = 1;
+ *         repeated float b = 2;
+ *         optional Bar bar = 3;
+ *         repeated Bar repeated_bar = 4;
+ *     }
+ * This will generate printer methods for them:
+ *      TYPE_PRINTER(Bar, PROPERTY_PRINT(aa));
+ *      TYPE_PRINTER(Foo,
+ *              PROPERTY_PRINT(a)
+ *              PROPERTY_PRINT(b)
+ *              PROPERTY_PRINT(bar)
+ *              REPEATED_PROPERTY_PRINT(repeated_bar)
+ *      );
+ */
+#define TYPE_PRINTER(typename, properties)                       \
+    inline void PrintTo(const typename& obj, std::ostream* os) { \
+        *os << #typename << ": { ";                              \
+        properties                                               \
+        *os << "}";                                              \
+    }
+
+EQ_MATCHER(PackageInfo,
+        PROPERTY_EQ(PackageInfo, version),
+        PROPERTY_EQ(PackageInfo, uid),
+        PROPERTY_EQ(PackageInfo, deleted),
+        PROPERTY_EQ(PackageInfo, truncated_certificate_hash),
+        PROPERTY_EQ(PackageInfo, name_hash),
+        PROPERTY_EQ(PackageInfo, version_string_hash),
+        PROPERTY_EQ(PackageInfo, name),
+        PROPERTY_EQ(PackageInfo, version_string),
+        PROPERTY_EQ(PackageInfo, installer_index),
+        PROPERTY_EQ(PackageInfo, installer_hash),
+        PROPERTY_EQ(PackageInfo, installer)
+);
+TYPE_PRINTER(PackageInfo,
+        PROPERTY_PRINT(version)
+        PROPERTY_PRINT(uid)
+        PROPERTY_PRINT(deleted)
+        PROPERTY_PRINT(truncated_certificate_hash)
+        PROPERTY_PRINT(name_hash)
+        PROPERTY_PRINT(version_string_hash)
+        PROPERTY_PRINT(name)
+        PROPERTY_PRINT(version_string)
+        PROPERTY_PRINT(installer_index)
+        PROPERTY_PRINT(installer_hash)
+        PROPERTY_PRINT(installer)
+);
+
+EQ_MATCHER(AttributionNode,
+        PROPERTY_EQ(AttributionNode, uid),
+        PROPERTY_EQ(AttributionNode, tag)
+);
+TYPE_PRINTER(AttributionNode,
+        PROPERTY_PRINT(uid)
+        PROPERTY_PRINT(tag)
+);
+
+EQ_MATCHER(ScreenStateChanged, PROPERTY_EQ(ScreenStateChanged, state));
+TYPE_PRINTER(ScreenStateChanged, PROPERTY_PRINT(state));
+
+EQ_MATCHER(TrainExperimentIds,
+        REPEATED_PROPERTY_EQ(TrainExperimentIds, experiment_id)
+);
+TYPE_PRINTER(TrainExperimentIds, REPEATED_PROPERTY_PRINT(experiment_id));
+
+EQ_MATCHER(TestAtomReported,
+        REPEATED_PROPERTY_MATCHER(TestAtomReported, attribution_node, EqAttributionNode),
+        PROPERTY_EQ(TestAtomReported, int_field),
+        PROPERTY_EQ(TestAtomReported, long_field),
+        PROPERTY_EQ(TestAtomReported, float_field),
+        PROPERTY_EQ(TestAtomReported, string_field),
+        PROPERTY_EQ(TestAtomReported, boolean_field),
+        PROPERTY_EQ(TestAtomReported, state),
+        PROPERTY_MATCHER(TestAtomReported, bytes_field, EqTrainExperimentIds),
+        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_int_field),
+        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_long_field),
+        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_float_field),
+        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_string_field),
+        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_boolean_field),
+        REPEATED_PROPERTY_EQ(TestAtomReported, repeated_enum_field)
+);
+TYPE_PRINTER(TestAtomReported,
+        REPEATED_PROPERTY_PRINT(attribution_node)
+        PROPERTY_PRINT(int_field)
+        PROPERTY_PRINT(long_field)
+        PROPERTY_PRINT(float_field)
+        PROPERTY_PRINT(string_field)
+        PROPERTY_PRINT(boolean_field)
+        PROPERTY_PRINT(state)
+        PROPERTY_PRINT(bytes_field)
+        REPEATED_PROPERTY_PRINT(repeated_int_field)
+        REPEATED_PROPERTY_PRINT(repeated_long_field)
+        REPEATED_PROPERTY_PRINT(repeated_float_field)
+        REPEATED_PROPERTY_PRINT(repeated_string_field)
+        REPEATED_PROPERTY_PRINT(repeated_boolean_field)
+        REPEATED_PROPERTY_PRINT(repeated_enum_field)
+);
+
+EQ_MATCHER(CpuActiveTime,
+        PROPERTY_EQ(CpuActiveTime, uid),
+        PROPERTY_EQ(CpuActiveTime, time_millis)
+);
+TYPE_PRINTER(CpuActiveTime,
+        PROPERTY_PRINT(uid)
+        PROPERTY_PRINT(time_millis)
+);
+
+EQ_MATCHER(PluggedStateChanged, PROPERTY_EQ(PluggedStateChanged, state));
+TYPE_PRINTER(PluggedStateChanged, PROPERTY_PRINT(state));
+
+EQ_MATCHER(Atom,
+        PROPERTY_MATCHER(Atom, screen_state_changed, EqScreenStateChanged),
+        PROPERTY_MATCHER(Atom, test_atom_reported, EqTestAtomReported)
+);
+TYPE_PRINTER(Atom,
+        PROPERTY_PRINT(screen_state_changed)
+        PROPERTY_PRINT(test_atom_reported)
+);
+
+EQ_MATCHER(ShellData,
+        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
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/lib/libstatspull/Android.bp b/lib/libstatspull/Android.bp
index f8d2ff8..99c3e54 100644
--- a/lib/libstatspull/Android.bp
+++ b/lib/libstatspull/Android.bp
@@ -24,6 +24,8 @@
 cc_defaults {
     name: "libstatspull_defaults",
     srcs: [
+        "stats_subscription.cpp",
+        "stats_provider.cpp",
         "stats_pull_atom_callback.cpp",
     ],
     cflags: [
@@ -105,16 +107,37 @@
     ],
 }
 
-// Note: These unit tests only test PullAtomMetadata.
-// For full E2E tests of libstatspull, use LibStatsPullTests
+// Note: These unit tests only test PullAtomMetadata and subscriptions
+// For full E2E tests of pullers, use LibStatsPullTests
 cc_test {
     name: "libstatspull_test",
     srcs: [
+        ":libprotobuf-internal-descriptor-proto",
+        ":libstats_log_protos",
+        ":libstats_subscription_protos",
         "tests/pull_atom_metadata_test.cpp",
+        "tests/stats_subscription_test.cpp",
     ],
+    proto: {
+        type: "lite",
+        include_dirs: [
+            "external/protobuf/src",
+        ],
+        static: true,
+    },
     shared_libs: [
         "libstatspull",
         "libstatssocket",
+        "libbase",
+        "libbinder",
+        "libutils",
+        "liblog",
+    ],
+    static_libs: [
+        "libgmock",
+        "libstatsgtestmatchers",
+        "libstatslog_statsdtest",
+        "libprotobuf-cpp-lite",
     ],
     test_suites: [
         "general-tests",
diff --git a/lib/libstatspull/include/stats_subscription.h b/lib/libstatspull/include/stats_subscription.h
new file mode 100644
index 0000000..6509c2b
--- /dev/null
+++ b/lib/libstatspull/include/stats_subscription.h
@@ -0,0 +1,127 @@
+/*
+ * 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 <stdint.h>
+#include <sys/cdefs.h>
+
+#ifndef __STATSD_SUBS_MIN_API__
+#define __STATSD_SUBS_MIN_API__ __ANDROID_API_U__
+#endif
+
+__BEGIN_DECLS
+
+/**
+ * Reason codes for why subscription callback was triggered.
+ */
+typedef enum AStatsManager_SubscriptionCallbackReason : uint32_t {
+    /**
+     * SubscriptionCallbackReason constant for subscription data transfer initiated by stats
+     * service.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_STATSD_INITIATED = 1,
+
+    /**
+     * SubscriptionCallbackReason constant for subscriber requesting flush of pending data.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_FLUSH_REQUESTED = 2,
+
+    /**
+     * SubscriptionCallbackReason constant for final stream of data for a subscription.
+     *
+     * Introduced in API 34.
+     */
+    ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED = 3,
+} AStatsManager_SubscriptionCallbackReason;
+
+/**
+ * Callback interface for receiving subscription data by the stats service.
+ *
+ * This will be called on an arbitrary binder thread. There is a pool of such threads and there is a
+ * no guarantee a single thread will be used even for the same subscription. Clients must ensure it
+ * is safe to call callback from arbitrary threads.
+ *
+ * \param subscription_id the subscription id for which the callback is triggered.
+ * \param reason code for why the callback is triggered.
+ * \param payload encoded SubscriptionResults proto containing subscription data.
+ *        Cannot be null.
+ * \param num_bytes size in bytes of the payload.
+ * \param cookie the opaque pointer passed in AStatsManager_addSubscription. Can be null.
+ *
+ * Introduced in API 34.
+ */
+typedef void (*AStatsManager_SubscriptionCallback)(int32_t subscription_id,
+                                                   AStatsManager_SubscriptionCallbackReason reason,
+                                                   uint8_t* _Nonnull payload, size_t num_bytes,
+                                                   void* _Nullable cookie);
+
+/**
+ * Adds a new subscription.
+ *
+ * Requires caller is in the traced_probes selinux domain.
+ *
+ * \param subscription_config encoded ShellSubscription proto containing parameters for a new
+ *        subscription. Cannot be null.
+ * \param num_bytes size in bytes of the subscription_config.
+ * \param callback function called to deliver subscription data back to the subscriber. Each
+ *        callback can be used for more than one subscription. Cannot be null.
+ * \param cookie opaque pointer to associate with the subscription. The provided callback will be
+ *        invoked with this cookie as an argument when delivering data for this subscription. Can be
+ *        null.
+ * \return subscription ID for the new subscription. Subscription ID is a positive integer. A
+ * negative value indicates an error.
+ *
+ * Introduced in API 34.
+ */
+int32_t AStatsManager_addSubscription(const uint8_t* _Nonnull subscription_config, size_t num_bytes,
+                                      const AStatsManager_SubscriptionCallback _Nonnull callback,
+                                      void* _Nullable cookie)
+        __INTRODUCED_IN(__STATSD_SUBS_MIN_API__);
+
+/**
+ * Removes an existing subscription.
+ * This will trigger a flush of the remaining subscription data through
+ * AStatsManager_SubscriptionCallback with the reason as
+ * ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED.
+ *
+ * Requires caller is in the traced_probes selinux domain.
+ *
+ * \param subscription_id subscription id of the subscription to terminate.
+ *
+ * Introduced in API 34.
+ */
+void AStatsManager_removeSubscription(int32_t subscription_id)
+        __INTRODUCED_IN(__STATSD_SUBS_MIN_API__);
+
+/**
+ * Request stats service to flush a subscription.
+ * This will trigger AStatsManager_SubscriptionCallback with the reason as
+ * ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_FLUSH_REQUESTED.
+ *
+ * Requires caller is in the traced_probes selinux domain.
+ *
+ * \param subscription_id ID of the subscription to be flushed.
+ *
+ * Introduced in API 34.
+ */
+void AStatsManager_flushSubscription(int32_t subscription_id)
+        __INTRODUCED_IN(__STATSD_SUBS_MIN_API__);
+
+__END_DECLS
diff --git a/lib/libstatspull/libstatspull.map.txt b/lib/libstatspull/libstatspull.map.txt
index e0e851a..30641e4 100644
--- a/lib/libstatspull/libstatspull.map.txt
+++ b/lib/libstatspull/libstatspull.map.txt
@@ -12,6 +12,10 @@
         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
     local:
         *;
 };
diff --git a/lib/libstatspull/libstatspull_test.xml b/lib/libstatspull/libstatspull_test.xml
index 233fc1f..5b06f1e 100644
--- a/lib/libstatspull/libstatspull_test.xml
+++ b/lib/libstatspull/libstatspull_test.xml
@@ -29,6 +29,7 @@
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
         <option name="module-name" value="libstatspull_test" />
+        <option name="native-test-timeout" value="180000"/>
     </test>
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
diff --git a/lib/libstatspull/stats_provider.cpp b/lib/libstatspull/stats_provider.cpp
new file mode 100644
index 0000000..bee8e89
--- /dev/null
+++ b/lib/libstatspull/stats_provider.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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 <android/binder_manager.h>
+#include <stats_provider.h>
+
+using aidl::android::os::IStatsd;
+
+StatsProvider::StatsProvider(StatsProviderBinderDiedCallback callback)
+    : mDeathRecipient(AIBinder_DeathRecipient_new(binderDied)), mCallback(callback) {
+}
+
+StatsProvider::~StatsProvider() {
+    resetStatsService();
+}
+
+std::shared_ptr<IStatsd> StatsProvider::getStatsService() {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!mStatsd) {
+        // Fetch statsd
+        ::ndk::SpAIBinder binder(AServiceManager_getService("stats"));
+        mStatsd = IStatsd::fromBinder(binder);
+        if (mStatsd) {
+            AIBinder_linkToDeath(binder.get(), mDeathRecipient.get(), this);
+        }
+    }
+    return mStatsd;
+}
+
+void StatsProvider::resetStatsService() {
+    std::lock_guard<std::mutex> lock(mMutex);
+    mStatsd = nullptr;
+}
+
+void StatsProvider::binderDied(void* cookie) {
+    StatsProvider* statsProvider = static_cast<StatsProvider*>(cookie);
+    statsProvider->resetStatsService();
+    statsProvider->mCallback();
+}
diff --git a/lib/libstatspull/stats_provider.h b/lib/libstatspull/stats_provider.h
new file mode 100644
index 0000000..c9d9246
--- /dev/null
+++ b/lib/libstatspull/stats_provider.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <aidl/android/os/IStatsd.h>
+#include <android/binder_auto_utils.h>
+
+using StatsProviderBinderDiedCallback = void (*)(void);
+
+/**
+ * Wrapper class for providing IStatsd Binder service.
+ * It handles Binder death and registers a callback for when the Binder service is restored after
+ * death.
+ */
+class StatsProvider {
+public:
+    StatsProvider(StatsProviderBinderDiedCallback callback);
+
+    ~StatsProvider();
+
+    std::shared_ptr<aidl::android::os::IStatsd> getStatsService();
+
+private:
+    static void binderDied(void* cookie);
+
+    void resetStatsService();
+
+    std::mutex mMutex;
+    std::shared_ptr<aidl::android::os::IStatsd> mStatsd;
+    const ::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
+    const StatsProviderBinderDiedCallback mCallback;
+};
diff --git a/lib/libstatspull/stats_pull_atom_callback.cpp b/lib/libstatspull/stats_pull_atom_callback.cpp
index 8c72944..3fdf243 100644
--- a/lib/libstatspull/stats_pull_atom_callback.cpp
+++ b/lib/libstatspull/stats_pull_atom_callback.cpp
@@ -47,7 +47,7 @@
 }
 
 constexpr int64_t DEFAULT_COOL_DOWN_MILLIS = 1000LL;  // 1 second.
-constexpr int64_t DEFAULT_TIMEOUT_MILLIS = 2000LL;    // 2 seconds.
+constexpr int64_t DEFAULT_TIMEOUT_MILLIS = 1500LL;    // 1.5 seconds.
 
 struct AStatsManager_PullAtomMetadata {
     int64_t cool_down_millis;
diff --git a/lib/libstatspull/stats_subscription.cpp b/lib/libstatspull/stats_subscription.cpp
new file mode 100644
index 0000000..3bc7e79
--- /dev/null
+++ b/lib/libstatspull/stats_subscription.cpp
@@ -0,0 +1,185 @@
+/*
+ * 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 <aidl/android/os/BnStatsSubscriptionCallback.h>
+#include <aidl/android/os/IStatsd.h>
+#include <aidl/android/os/StatsSubscriptionCallbackReason.h>
+#include <android/binder_auto_utils.h>
+#include <stats_provider.h>
+#include <stats_subscription.h>
+
+#include <atomic>
+#include <map>
+#include <vector>
+
+using Status = ::ndk::ScopedAStatus;
+using aidl::android::os::BnStatsSubscriptionCallback;
+using aidl::android::os::IStatsd;
+using aidl::android::os::StatsSubscriptionCallbackReason;
+using ::ndk::SharedRefBase;
+
+class Subscription;
+
+// Mutex for accessing subscriptions map.
+static std::mutex subscriptionsMutex;
+
+// TODO(b/271039569): Store subscriptions in a singleton object.
+// Added subscriptions keyed by their subscription ID.
+static std::map</* subscription ID */ int32_t, std::shared_ptr<Subscription>> subscriptions;
+
+class Subscription : public BnStatsSubscriptionCallback {
+public:
+    Subscription(const int32_t subscriptionId, const std::vector<uint8_t>& subscriptionConfig,
+                 const AStatsManager_SubscriptionCallback callback, void* cookie)
+        : mSubscriptionId(subscriptionId),
+          mSubscriptionParamsBytes(subscriptionConfig),
+          mCallback(callback),
+          mCookie(cookie) {
+    }
+
+    Status onSubscriptionData(const StatsSubscriptionCallbackReason reason,
+                              const std::vector<uint8_t>& subscriptionPayload) override {
+        std::vector<uint8_t> mutablePayload = subscriptionPayload;
+        mCallback(mSubscriptionId, static_cast<AStatsManager_SubscriptionCallbackReason>(reason),
+                  mutablePayload.data(), mutablePayload.size(), mCookie);
+
+        std::shared_ptr<Subscription> thisSubscription;
+        if (reason == StatsSubscriptionCallbackReason::SUBSCRIPTION_ENDED) {
+            std::lock_guard<std::mutex> lock(subscriptionsMutex);
+
+            auto subscriptionsIt = subscriptions.find(mSubscriptionId);
+            if (subscriptionsIt != subscriptions.end()) {
+                // Ensure this subscription's refcount doesn't hit 0 when we erase it from the
+                // subscriptions map by adding a local reference here.
+                thisSubscription = subscriptionsIt->second;
+
+                subscriptions.erase(subscriptionsIt);
+            }
+        }
+
+        return Status::ok();
+    }
+
+    const std::vector<uint8_t>& getSubscriptionParamsBytes() const {
+        return mSubscriptionParamsBytes;
+    }
+
+private:
+    const int32_t mSubscriptionId;
+    const std::vector<uint8_t> mSubscriptionParamsBytes;
+    const AStatsManager_SubscriptionCallback mCallback;
+    void* mCookie;
+};
+
+// forward declare so it can be referenced in StatsProvider constructor.
+static void onStatsBinderRestart();
+
+static std::shared_ptr<StatsProvider> statsProvider =
+        std::make_shared<StatsProvider>(onStatsBinderRestart);
+
+static void onStatsBinderRestart() {
+    const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
+    if (statsService == nullptr) {
+        return;
+    }
+
+    // Since we do not want to make an IPC with the lock held, we first create a
+    // copy of the data with the lock held before iterating through the map.
+    std::map<int32_t, std::shared_ptr<Subscription>> subscriptionsCopy;
+    {
+        std::lock_guard<std::mutex> lock(subscriptionsMutex);
+        subscriptionsCopy = subscriptions;
+    }
+    for (const auto& [_, subscription] : subscriptionsCopy) {
+        statsService->addSubscription(subscription->getSubscriptionParamsBytes(), subscription);
+    }
+}
+
+static int32_t getNextSubscriptionId() {
+    static std::atomic_int32_t nextSubscriptionId(0);
+    return ++nextSubscriptionId;
+}
+
+static std::shared_ptr<Subscription> getBinderCallbackForSubscription(
+        const int32_t subscription_id) {
+    std::lock_guard<std::mutex> lock(subscriptionsMutex);
+    auto subscriptionsIt = subscriptions.find(subscription_id);
+    if (subscriptionsIt == subscriptions.end()) {
+        return nullptr;
+    }
+    return subscriptionsIt->second;
+}
+
+int32_t AStatsManager_addSubscription(const uint8_t* subscription_config, const size_t num_bytes,
+                                      const AStatsManager_SubscriptionCallback callback,
+                                      void* cookie) {
+    const std::vector<uint8_t> subscriptionConfig(subscription_config,
+                                                  subscription_config + num_bytes);
+    const int32_t subscriptionId(getNextSubscriptionId());
+    std::shared_ptr<Subscription> subscription =
+            SharedRefBase::make<Subscription>(subscriptionId, subscriptionConfig, callback, cookie);
+
+    {
+        std::lock_guard<std::mutex> lock(subscriptionsMutex);
+
+        subscriptions[subscriptionId] = subscription;
+    }
+
+    // TODO(b/270648168): Queue the binder call to not block on binder
+    const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
+    if (statsService != nullptr) {
+        statsService->addSubscription(subscriptionConfig, subscription);
+    }
+
+    return subscriptionId;
+}
+
+void AStatsManager_removeSubscription(const int32_t subscription_id) {
+    std::shared_ptr<Subscription> subscription = getBinderCallbackForSubscription(subscription_id);
+    if (subscription == nullptr) {
+        return;
+    }
+
+    // TODO(b/270648168): Queue the binder call to not block on binder
+    const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
+    if (statsService == nullptr) {
+        // Statsd not available.
+        // TODO(b/270656443): keep track of removeSubscription request and make the IPC call when
+        // statsd binder comes back up.
+        return;
+    }
+    statsService->removeSubscription(subscription);
+}
+
+void AStatsManager_flushSubscription(const int32_t subscription_id) {
+    std::shared_ptr<Subscription> subscription = getBinderCallbackForSubscription(subscription_id);
+    if (subscription == nullptr) {
+        return;
+    }
+
+    // TODO(b/270648168): Queue the binder call to not block on binder
+    const std::shared_ptr<IStatsd> statsService = statsProvider->getStatsService();
+    if (statsService == nullptr) {
+        // Statsd not available.
+        // TODO(b/270656443): keep track of flushSubscription request and make the IPC call when
+        // statsd binder comes back up.
+        return;
+    }
+
+    // TODO(b/273649282): Ensure the subscription is cleared in case the final Binder data
+    // callback fails.
+    statsService->flushSubscription(subscription);
+}
diff --git a/lib/libstatspull/tests/pull_atom_metadata_test.cpp b/lib/libstatspull/tests/pull_atom_metadata_test.cpp
index abc8e47..c3ebe5b 100644
--- a/lib/libstatspull/tests/pull_atom_metadata_test.cpp
+++ b/lib/libstatspull/tests/pull_atom_metadata_test.cpp
@@ -21,7 +21,7 @@
 namespace {
 
 static const int64_t DEFAULT_COOL_DOWN_MILLIS = 1000LL;  // 1 second.
-static const int64_t DEFAULT_TIMEOUT_MILLIS = 2000LL;    // 2 seconds.
+static const int64_t DEFAULT_TIMEOUT_MILLIS = 1500LL;    // 1.5 seconds.
 
 }  // anonymous namespace
 
diff --git a/lib/libstatspull/tests/stats_subscription_test.cpp b/lib/libstatspull/tests/stats_subscription_test.cpp
new file mode 100644
index 0000000..301a937
--- /dev/null
+++ b/lib/libstatspull/tests/stats_subscription_test.cpp
@@ -0,0 +1,314 @@
+/*
+ * 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 <binder/ProcessState.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gtest_matchers.h>
+#include <stats_subscription.h>
+#include <stdint.h>
+#include <utils/Looper.h>
+
+#include <chrono>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include "packages/modules/StatsD/statsd/src/shell/shell_config.pb.h"
+#include "packages/modules/StatsD/statsd/src/shell/shell_data.pb.h"
+#include "statslog_statsdtest.h"
+
+#ifdef __ANDROID__
+
+using namespace testing;
+using android::Looper;
+using android::ProcessState;
+using android::sp;
+using android::os::statsd::Atom;
+using android::os::statsd::ShellData;
+using android::os::statsd::ShellSubscription;
+using android::os::statsd::TestAtomReported_State_OFF;
+using android::os::statsd::TrainExperimentIds;
+using android::os::statsd::util::BytesField;
+using android::os::statsd::util::SCREEN_BRIGHTNESS_CHANGED;
+using android::os::statsd::util::stats_write;
+using android::os::statsd::util::TEST_ATOM_REPORTED;
+using android::os::statsd::util::TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF;
+using std::string;
+using std::vector;
+using std::this_thread::sleep_for;
+
+namespace {
+
+class SubscriptionTest : public Test {
+public:
+    SubscriptionTest() : looper(Looper::prepare(/*opts=*/0)) {
+        const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+
+        *trainExpIds.mutable_experiment_id() = {expIds.begin(), expIds.end()};
+        trainExpIds.SerializeToString(&trainExpIdsBytes);
+    }
+
+    ~SubscriptionTest() {
+        const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+
+protected:
+    void SetUp() override {
+        // Start the Binder thread pool.
+        ProcessState::self()->startThreadPool();
+    }
+
+    void TearDown() {
+        // Clear any dangling subscriptions from statsd.
+        if (__builtin_available(android __STATSD_SUBS_MIN_API__, *)) {
+            AStatsManager_removeSubscription(subId);
+        }
+    }
+
+    void LogTestAtomReported(int32_t intFieldValue) {
+        const BytesField bytesField(trainExpIdsBytes.data(), trainExpIdsBytes.size());
+        stats_write(TEST_ATOM_REPORTED, uids.data(), uids.size(), tags, intFieldValue,
+                    /*long_field=*/2LL, /*float_field=*/3.0F,
+                    /*string_field=*/string1.c_str(),
+                    /*boolean_field=*/false, /*state=*/TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF,
+                    bytesField, repeatedInts, repeatedLongs, repeatedFloats, repeatedStrings,
+                    &(repeatedBool[0]), /*repeatedBoolSize=*/2, repeatedEnums);
+    }
+
+    int32_t subId;
+
+    // TestAtomReported fields.
+    const vector<int32_t> uids = {1};
+    const string tag = "test";
+    const vector<char const*> tags = {tag.c_str()};
+
+    // 100 int64s for the MODE_BYTES field to push atom size to over 1K.
+    const vector<int64_t> expIds = vector<int64_t>(100, INT64_MAX);
+
+    const vector<int32_t> repeatedInts{1};
+    const vector<int64_t> repeatedLongs{2LL};
+    const vector<float> repeatedFloats{3.0F};
+    const string string1 = "ABC";
+    const vector<char const*> repeatedStrings = {string1.c_str()};
+    const bool repeatedBool[2] = {false, true};
+    const vector<int32_t> repeatedEnums = {TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF};
+    TrainExperimentIds trainExpIds;
+    string trainExpIdsBytes;
+
+private:
+    sp<Looper> looper;
+};
+
+// Stores arguments passed in subscription callback.
+struct CallbackData {
+    int32_t subId;
+    AStatsManager_SubscriptionCallbackReason reason;
+    vector<uint8_t> payload;
+    int count;  // Stores number of times the callback is invoked.
+};
+
+static void callback(int32_t subscription_id, AStatsManager_SubscriptionCallbackReason reason,
+                     uint8_t* _Nonnull payload, size_t num_bytes, void* _Nullable cookie) {
+    CallbackData* data = static_cast<CallbackData*>(cookie);
+    data->subId = subscription_id;
+    data->reason = reason;
+    data->payload.assign(payload, payload + num_bytes);
+    data->count++;
+}
+
+constexpr static int WAIT_MS = 500;
+
+TEST_F(SubscriptionTest, TestSubscription) {
+    if (__builtin_available(android __STATSD_SUBS_MIN_API__, *)) {
+        ShellSubscription config;
+        config.add_pushed()->set_atom_id(TEST_ATOM_REPORTED);
+        config.add_pushed()->set_atom_id(SCREEN_BRIGHTNESS_CHANGED);
+
+        string configBytes;
+        config.SerializeToString(&configBytes);
+
+        CallbackData callbackData{/*subId=*/0,
+                                  ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED,
+                                  /*payload=*/{},
+                                  /*count=*/0};
+
+        // Add subscription.
+        subId = AStatsManager_addSubscription(reinterpret_cast<const uint8_t*>(configBytes.data()),
+                                              configBytes.size(), &callback, &callbackData);
+        ASSERT_GT(subId, 0);
+        sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+        // Log events without exceeding statsd cache.
+        stats_write(SCREEN_BRIGHTNESS_CHANGED, 100);
+        LogTestAtomReported(1);
+        sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+        // Verify no callback occurred yet.
+        EXPECT_EQ(callbackData.subId, 0);
+        EXPECT_EQ(callbackData.reason,
+                  ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED);
+        EXPECT_EQ(callbackData.count, 0);
+        ASSERT_TRUE(callbackData.payload.empty());
+
+        // Log another TestAtomReported to overflow cache.
+        LogTestAtomReported(2);
+        sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+        // Verify callback occurred.
+        EXPECT_EQ(callbackData.subId, subId);
+        EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_STATSD_INITIATED);
+        EXPECT_EQ(callbackData.count, 1);
+        ASSERT_GT(callbackData.payload.size(), 0);
+
+        ShellData actualShellData;
+        ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
+                                                   callbackData.payload.size()));
+
+        ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 3);
+        EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
+
+        ASSERT_GE(actualShellData.atom_size(), 3);
+
+        // Verify atom 1.
+        Atom expectedAtom;
+        expectedAtom.mutable_screen_brightness_changed()->set_level(100);
+        EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
+
+        // Verify atom 2.
+        expectedAtom.Clear();
+        auto* testAtomReported = expectedAtom.mutable_test_atom_reported();
+        auto* attributionNode = testAtomReported->add_attribution_node();
+        attributionNode->set_uid(uids[0]);
+        attributionNode->set_tag(tag);
+        testAtomReported->set_int_field(1);
+        testAtomReported->set_long_field(2LL);
+        testAtomReported->set_float_field(3.0F);
+        testAtomReported->set_string_field(string1);
+        testAtomReported->set_boolean_field(false);
+        testAtomReported->set_state(TestAtomReported_State_OFF);
+        *testAtomReported->mutable_bytes_field() = trainExpIds;
+        *testAtomReported->mutable_repeated_int_field() = {repeatedInts.begin(),
+                                                           repeatedInts.end()};
+        *testAtomReported->mutable_repeated_long_field() = {repeatedLongs.begin(),
+                                                            repeatedLongs.end()};
+        *testAtomReported->mutable_repeated_float_field() = {repeatedFloats.begin(),
+                                                             repeatedFloats.end()};
+        *testAtomReported->mutable_repeated_string_field() = {repeatedStrings.begin(),
+                                                              repeatedStrings.end()};
+        *testAtomReported->mutable_repeated_boolean_field() = {&repeatedBool[0],
+                                                               &repeatedBool[0] + 2};
+        *testAtomReported->mutable_repeated_enum_field() = {repeatedEnums.begin(),
+                                                            repeatedEnums.end()};
+        EXPECT_THAT(actualShellData.atom(1), EqAtom(expectedAtom));
+
+        // Verify atom 3.
+        testAtomReported->set_int_field(2);
+        EXPECT_THAT(actualShellData.atom(2), EqAtom(expectedAtom));
+
+        // Log another ScreenBrightnessChanged atom. No callback should occur.
+        stats_write(SCREEN_BRIGHTNESS_CHANGED, 99);
+        sleep_for(std::chrono::milliseconds(WAIT_MS));
+        EXPECT_EQ(callbackData.count, 1);
+
+        // Flush subscription. Callback should occur.
+        AStatsManager_flushSubscription(subId);
+        sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+        EXPECT_EQ(callbackData.subId, subId);
+        EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_FLUSH_REQUESTED);
+        EXPECT_EQ(callbackData.count, 2);
+        ASSERT_GT(callbackData.payload.size(), 0);
+
+        ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
+                                                   callbackData.payload.size()));
+
+        ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
+        EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
+
+        ASSERT_GE(actualShellData.atom_size(), 1);
+
+        // Verify atom 1.
+        expectedAtom.Clear();
+        expectedAtom.mutable_screen_brightness_changed()->set_level(99);
+        EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
+
+        // Log another ScreenBrightnessChanged atom. No callback should occur.
+        stats_write(SCREEN_BRIGHTNESS_CHANGED, 98);
+        sleep_for(std::chrono::milliseconds(WAIT_MS));
+        EXPECT_EQ(callbackData.count, 2);
+
+        // Trigger callback through cache timeout.
+        // Two 500 ms sleeps have occurred already so the total sleep is 71000 ms since last
+        // callback invocation.
+        sleep_for(std::chrono::milliseconds(70'000));
+        EXPECT_EQ(callbackData.subId, subId);
+        EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_STATSD_INITIATED);
+        EXPECT_EQ(callbackData.count, 3);
+        ASSERT_GT(callbackData.payload.size(), 0);
+
+        ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
+                                                   callbackData.payload.size()));
+
+        ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
+        EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
+
+        ASSERT_GE(actualShellData.atom_size(), 1);
+
+        // Verify atom 1.
+        expectedAtom.Clear();
+        expectedAtom.mutable_screen_brightness_changed()->set_level(98);
+        EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
+
+        // Log another ScreenBrightnessChanged atom. No callback should occur.
+        stats_write(SCREEN_BRIGHTNESS_CHANGED, 97);
+        sleep_for(std::chrono::milliseconds(WAIT_MS));
+        EXPECT_EQ(callbackData.count, 3);
+
+        // End subscription. Final callback should occur.
+        AStatsManager_removeSubscription(subId);
+        sleep_for(std::chrono::milliseconds(WAIT_MS));
+
+        EXPECT_EQ(callbackData.subId, subId);
+        EXPECT_EQ(callbackData.reason,
+                  ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED);
+        EXPECT_EQ(callbackData.count, 4);
+        ASSERT_GT(callbackData.payload.size(), 0);
+
+        ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
+                                                   callbackData.payload.size()));
+
+        ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
+        EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
+
+        ASSERT_GE(actualShellData.atom_size(), 1);
+
+        // Verify atom 1.
+        expectedAtom.Clear();
+        expectedAtom.mutable_screen_brightness_changed()->set_level(97);
+        EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
+    } else {
+        GTEST_SKIP();
+    }
+}
+
+}  // namespace
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
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/statsd_writer.c b/lib/libstatssocket/statsd_writer.c
index 06695fe..2c9bb25 100644
--- a/lib/libstatssocket/statsd_writer.c
+++ b/lib/libstatssocket/statsd_writer.c
@@ -107,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.
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 c3771df..367bfdd 100644
--- a/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/service/java/com/android/server/stats/StatsCompanionService.java
@@ -103,8 +103,6 @@
 
     public static final int DEATH_THRESHOLD = 10;
 
-    private static final String INCLUDE_CERTIFICATE_HASH = "include_certificate_hash";
-
     private final Context mContext;
     private final AlarmManager mAlarmManager;
     @GuardedBy("sStatsdLock")
@@ -263,15 +261,12 @@
                                             | ProtoOutputStream.FIELD_COUNT_SINGLE
                                             | INSTALLER_FIELD_ID,
                                     installer);
-                            if (DeviceConfig.getBoolean(
-                                        NAMESPACE_STATSD_JAVA, INCLUDE_CERTIFICATE_HASH, false)) {
-                                final byte[] certHash = getPackageCertificateHash(
-                                        packagesPlusApex.get(j).signingInfo);
-                                output.write(ProtoOutputStream.FIELD_TYPE_BYTES
-                                                | ProtoOutputStream.FIELD_COUNT_SINGLE
-                                                | CERTIFICATE_HASH_FIELD_ID,
-                                        certHash);
-                            }
+                            final byte[] certHash =
+                                getPackageCertificateHash(packagesPlusApex.get(j).signingInfo);
+                            output.write(ProtoOutputStream.FIELD_TYPE_BYTES
+                                    | ProtoOutputStream.FIELD_COUNT_SINGLE
+                                    | CERTIFICATE_HASH_FIELD_ID,
+                                certHash);
 
                             numRecords++;
                             output.end(applicationInfoToken);
@@ -284,6 +279,7 @@
                 }
             } finally {
                 if (DEBUG) Log.d(TAG, "End thread for sending uid map data.");
+                FileUtils.closeQuietly(fout);
                 backgroundThread.quit();
             }
         });
@@ -376,11 +372,7 @@
                         final String installer = getInstallerPackageName(pm, app);
 
                         // Get Package certificate hash.
-                        byte[] certHash = new byte[0];
-                        if (DeviceConfig.getBoolean(
-                                    NAMESPACE_STATSD_JAVA, INCLUDE_CERTIFICATE_HASH, false)) {
-                            certHash = getPackageCertificateHash(pi.signingInfo);
-                        }
+                        byte[] certHash = getPackageCertificateHash(pi.signingInfo);
 
                         sStatsd.informOnePackage(
                                 app,
@@ -673,13 +665,6 @@
 
     private void onPropertiesChanged(final Properties properties) {
         updateProperties(properties);
-
-        // Re-fetch package information with package certificates if include_certificate_hash
-        // property changed.
-        final Set<String> propertyNames = properties.getKeyset();
-        if (propertyNames.contains(INCLUDE_CERTIFICATE_HASH)) {
-            informAllUids(mContext);
-        }
     }
 
     private void updateProperties(final Properties properties) {
@@ -710,7 +695,7 @@
         try {
             statsd.updateProperties(propertyParcels);
         } catch (RemoteException e) {
-            Log.w(TAG, "Failed to inform statsd of an include app certificate flag update", e);
+            Log.w(TAG, "Failed to inform statsd of updated statsd_java properties", e);
         }
     }
 
diff --git a/service/java/com/android/server/stats/StatsManagerService.java b/service/java/com/android/server/stats/StatsManagerService.java
index 1e3846b..c518f50 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.PowerManager;
 import android.os.Process;
@@ -66,6 +67,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();
@@ -104,6 +108,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;
@@ -477,10 +514,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.
@@ -589,6 +713,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 {
@@ -657,7 +783,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(),
@@ -665,4 +791,28 @@
             }
         }
     }
+
+    // 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());
+            }
+        }
+    }
 }
diff --git a/statsd/Android.bp b/statsd/Android.bp
index 8b34c4e..db3f28b 100644
--- a/statsd/Android.bp
+++ b/statsd/Android.bp
@@ -64,6 +64,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",
@@ -80,6 +81,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",
@@ -91,6 +93,8 @@
         "src/subscriber/SubscriberReporter.cpp",
         "src/uid_data.proto",
         "src/utils/MultiConditionTrigger.cpp",
+        "src/utils/DbUtils.cpp",
+        "src/utils/RestrictedPolicyManager.cpp",
         "src/utils/ShardOffsetProvider.cpp",
     ],
 
@@ -109,6 +113,7 @@
         "libutils",
         "server_configurable_flags",
         "statsd-aidl-ndk",
+        "libsqlite_static_noicu",
     ],
     shared_libs: [
         "libbinder_ndk",
@@ -301,6 +306,7 @@
         "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",
@@ -311,6 +317,9 @@
     ],
 
     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",
@@ -338,8 +347,10 @@
         "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/ValueMetric_pull_e2e_test.cpp",
         "tests/e2e/WakelockDuration_e2e_test.cpp",
+        "tests/e2e/RestrictedEventMetric_e2e_test.cpp",
         "tests/external/puller_util_test.cpp",
         "tests/external/StatsCallbackPuller_test.cpp",
         "tests/external/StatsPuller_test.cpp",
@@ -362,33 +373,39 @@
         "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",
+        "tests/LogEventFilter_test.cpp",
         "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",
         "tests/StatsService_test.cpp",
         "tests/storage/StorageManager_test.cpp",
         "tests/UidMap_test.cpp",
         "tests/utils/MultiConditionTrigger_test.cpp",
+        "tests/utils/DbUtils_test.cpp",
     ],
 
     static_libs: [
         "libgmock",
-        "libplatformprotos-test",
+        "libstatsgtestmatchers",
         "libstatslog_statsdtest",
         "libstatssocket_private",
     ],
 
     proto: {
-        type: "full",
+        type: "lite",
         include_dirs: [
             "external/protobuf/src",
+            "frameworks/proto_logging/stats",
         ],
+        static: true,
     },
     min_sdk_version: "30",
 
@@ -408,11 +425,13 @@
         ":libprotobuf-internal-protos",
         ":libstats_internal_protos",
 
+        "benchmark/db_benchmark.cpp",
         "benchmark/duration_metric_benchmark.cpp",
         "benchmark/filter_value_benchmark.cpp",
         "benchmark/get_dimensions_for_condition_benchmark.cpp",
         "benchmark/hello_world_benchmark.cpp",
         "benchmark/log_event_benchmark.cpp",
+        "benchmark/log_event_filter_benchmark.cpp",
         "benchmark/main.cpp",
         "benchmark/metric_util.cpp",
         "benchmark/stats_write_benchmark.cpp",
@@ -513,10 +532,19 @@
 
 // Filegroup for statsd config proto definition.
 filegroup {
-    name: "statsd-config-proto-def",
+    name: "libstats_config_protos",
     srcs: ["src/statsd_config.proto"],
 }
 
+// Filegroup for statsd report protos.
+filegroup {
+    name: "libstats_log_protos",
+    srcs: [
+        "src/stats_log.proto",
+        "src/guardrail/invalid_config_reason_enum.proto",
+    ],
+}
+
 // Filegroup for all statsd protos
 filegroup {
     name: "statsd_internal_protos",
@@ -532,3 +560,15 @@
         "src/guardrail/invalid_config_reason_enum.proto",
     ],
 }
+
+// Filegroup for subscription protos.
+filegroup {
+    name: "libstats_subscription_protos",
+    srcs: [
+        ":libstats_internal_protos",
+        ":libstats_config_protos",
+        "src/shell/shell_config.proto",
+        "src/shell/shell_data.proto",
+    ],
+}
+
diff --git a/statsd/benchmark/db_benchmark.cpp b/statsd/benchmark/db_benchmark.cpp
new file mode 100644
index 0000000..ebe751a
--- /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 "metric_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/log_event_benchmark.cpp b/statsd/benchmark/log_event_benchmark.cpp
index 057e00b..419f323 100644
--- a/statsd/benchmark/log_event_benchmark.cpp
+++ b/statsd/benchmark/log_event_benchmark.cpp
@@ -22,11 +22,19 @@
 namespace os {
 namespace statsd {
 
-static size_t createAndParseStatsEvent(uint8_t* msg) {
+static void writeEventTestFields(AStatsEvent& event) {
+    AStatsEvent_writeInt64(&event, 3L);
+    AStatsEvent_writeInt32(&event, 2);
+    AStatsEvent_writeFloat(&event, 2.0);
+    AStatsEvent_writeString(&event, "DemoStringValue");
+}
+
+static size_t createStatsEvent(uint8_t* msg, int numElements = 1) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
-    AStatsEvent_writeInt32(event, 2);
-    AStatsEvent_writeFloat(event, 2.0);
+    for (int i = 0; i < numElements; i++) {
+        writeEventTestFields(*event);
+    }
     AStatsEvent_build(event);
 
     size_t size;
@@ -35,9 +43,21 @@
     return size;
 }
 
+static size_t createStatsEventMedium(uint8_t* msg) {
+    return createStatsEvent(msg, 5);
+}
+
+static size_t createStatsEventLarge(uint8_t* msg) {
+    return createStatsEvent(msg, 10);
+}
+
+static size_t createStatsEventExtraLarge(uint8_t* msg) {
+    return createStatsEvent(msg, 40);
+}
+
 static void BM_LogEventCreation(benchmark::State& state) {
     uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
-    size_t size = createAndParseStatsEvent(msg);
+    size_t size = createStatsEvent(msg);
     while (state.KeepRunning()) {
         LogEvent event(/*uid=*/ 1000, /*pid=*/ 1001);
         benchmark::DoNotOptimize(event.parseBuffer(msg, size));
@@ -45,6 +65,147 @@
 }
 BENCHMARK(BM_LogEventCreation);
 
+static void BM_LogEventCreationWithPrefetch(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEvent(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        // explicitly parse header first
+        const LogEvent::BodyBufferInfo header = event.parseHeader(msg, size);
+
+        // explicitly parse body using the header
+        benchmark::DoNotOptimize(event.parseBody(header));
+    }
+}
+BENCHMARK(BM_LogEventCreationWithPrefetch);
+
+static void BM_LogEventCreationWithPrefetchOnly(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEvent(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        // explicitly parse header only and skip the body
+        benchmark::DoNotOptimize(event.parseHeader(msg, size));
+    }
+}
+BENCHMARK(BM_LogEventCreationWithPrefetchOnly);
+
+static void BM_LogEventCreationMedium(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEventMedium(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        benchmark::DoNotOptimize(event.parseBuffer(msg, size));
+    }
+}
+BENCHMARK(BM_LogEventCreationMedium);
+
+static void BM_LogEventCreationMediumWithPrefetch(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEventMedium(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        // explicitly parse header first
+        const LogEvent::BodyBufferInfo header = event.parseHeader(msg, size);
+
+        // explicitly parse body using the header
+        benchmark::DoNotOptimize(event.parseBody(header));
+    }
+}
+BENCHMARK(BM_LogEventCreationMediumWithPrefetch);
+
+static void BM_LogEventCreationMediumWithPrefetchOnly(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEventMedium(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        // explicitly parse header only and skip the body
+        benchmark::DoNotOptimize(event.parseHeader(msg, size));
+    }
+}
+BENCHMARK(BM_LogEventCreationMediumWithPrefetchOnly);
+
+static void BM_LogEventCreationLarge(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEventLarge(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        benchmark::DoNotOptimize(event.parseBuffer(msg, size));
+    }
+}
+BENCHMARK(BM_LogEventCreationLarge);
+
+static void BM_LogEventCreationLargeWithPrefetch(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEventLarge(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        // explicitly parse header first
+        const LogEvent::BodyBufferInfo header = event.parseHeader(msg, size);
+
+        // explicitly parse body using the header
+        benchmark::DoNotOptimize(event.parseBody(header));
+    }
+}
+BENCHMARK(BM_LogEventCreationLargeWithPrefetch);
+
+static void BM_LogEventCreationLargeWithPrefetchOnly(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEventLarge(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        // explicitly parse header only and skip the body
+        benchmark::DoNotOptimize(event.parseHeader(msg, size));
+    }
+}
+BENCHMARK(BM_LogEventCreationLargeWithPrefetchOnly);
+
+static void BM_LogEventCreationExtraLarge(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEventExtraLarge(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        benchmark::DoNotOptimize(event.parseBuffer(msg, size));
+    }
+}
+BENCHMARK(BM_LogEventCreationExtraLarge);
+
+static void BM_LogEventCreationExtraLargeWithPrefetch(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEventExtraLarge(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        // explicitly parse header first
+        const LogEvent::BodyBufferInfo header = event.parseHeader(msg, size);
+
+        // explicitly parse body using the header
+        benchmark::DoNotOptimize(event.parseBody(header));
+    }
+}
+BENCHMARK(BM_LogEventCreationExtraLargeWithPrefetch);
+
+static void BM_LogEventCreationExtraLargeWithPrefetchOnly(benchmark::State& state) {
+    uint8_t msg[LOGGER_ENTRY_MAX_PAYLOAD];
+    size_t size = createStatsEventExtraLarge(msg);
+    while (state.KeepRunning()) {
+        LogEvent event(/*uid=*/1000, /*pid=*/1001);
+
+        // explicitly parse header only and skip the body
+        benchmark::DoNotOptimize(event.parseHeader(msg, size));
+    }
+}
+BENCHMARK(BM_LogEventCreationExtraLargeWithPrefetchOnly);
+
 }  //  namespace statsd
 }  //  namespace os
 }  //  namespace android
diff --git a/statsd/benchmark/log_event_filter_benchmark.cpp b/statsd/benchmark/log_event_filter_benchmark.cpp
new file mode 100644
index 0000000..b65e88f
--- /dev/null
+++ b/statsd/benchmark/log_event_filter_benchmark.cpp
@@ -0,0 +1,140 @@
+/*
+ * 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 <random>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "socket/LogEventFilter.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+constexpr int kAtomIdsCount = 500;         //  Filter size setup
+constexpr int kAtomIdsSampleCount = 3000;  //  Queries number
+
+std::vector<int> generateSampleAtomIdsList() {
+    std::vector<int> atomIds(kAtomIdsSampleCount);
+
+    std::default_random_engine generator;
+
+    // Get atoms ids which are not in the filter to test behavior when set is searched for an
+    // an absent key
+    // Expected atoms ids are in a range 1..3000, random & evenly distributes
+    std::uniform_int_distribution<int> distribution(1, kAtomIdsSampleCount);
+
+    for (int i = 0; i < kAtomIdsSampleCount; ++i) {
+        atomIds[i] = distribution(generator);
+    }
+
+    return atomIds;
+}
+
+template <typename T>
+T generateAtomIds() {
+    T atomIds;
+
+    std::default_random_engine generator;
+    std::uniform_int_distribution<int> distribution(1, kAtomIdsCount);
+
+    for (int i = 0; i < kAtomIdsCount; ++i) {
+        atomIds.insert(distribution(generator));
+    }
+
+    return atomIds;
+}
+
+// Used to setup filter
+const std::set<int> kAtomIdsSet = generateAtomIds<std::set<int>>();
+const std::unordered_set<int> kAtomIdsUnorderedSet = generateAtomIds<std::unordered_set<int>>();
+
+const std::set<int> kAtomIdsSet2 = generateAtomIds<std::set<int>>();
+const std::unordered_set<int> kAtomIdsUnorderedSet2 = generateAtomIds<std::unordered_set<int>>();
+
+const std::set<int> kAtomIdsSet3 = generateAtomIds<std::set<int>>();
+const std::unordered_set<int> kAtomIdsUnorderedSet3 = generateAtomIds<std::unordered_set<int>>();
+
+const std::set<int> kAtomIdsSet4 = generateAtomIds<std::set<int>>();
+const std::unordered_set<int> kAtomIdsUnorderedSet4 = generateAtomIds<std::unordered_set<int>>();
+
+// Used to perform sample quieries
+const std::vector<int> kSampleIdsList = generateSampleAtomIdsList();
+
+}  // namespace
+
+static void BM_LogEventFilterUnorderedSet(benchmark::State& state) {
+    while (state.KeepRunning()) {
+        LogEventFilter eventFilter;
+        // populate
+        eventFilter.setAtomIds(kAtomIdsUnorderedSet, nullptr);
+        // many fetches
+        for (const auto& atomId : kSampleIdsList) {
+            benchmark::DoNotOptimize(eventFilter.isAtomInUse(atomId));
+        }
+    }
+}
+BENCHMARK(BM_LogEventFilterUnorderedSet);
+
+static void BM_LogEventFilterUnorderedSet2Consumers(benchmark::State& state) {
+    while (state.KeepRunning()) {
+        LogEventFilter eventFilter;
+        // populate
+        eventFilter.setAtomIds(kAtomIdsUnorderedSet, &kAtomIdsUnorderedSet);
+        eventFilter.setAtomIds(kAtomIdsUnorderedSet2, &kAtomIdsUnorderedSet2);
+        eventFilter.setAtomIds(kAtomIdsUnorderedSet3, &kAtomIdsUnorderedSet);
+        eventFilter.setAtomIds(kAtomIdsUnorderedSet4, &kAtomIdsUnorderedSet2);
+        // many fetches
+        for (const auto& atomId : kSampleIdsList) {
+            benchmark::DoNotOptimize(eventFilter.isAtomInUse(atomId));
+        }
+    }
+}
+BENCHMARK(BM_LogEventFilterUnorderedSet2Consumers);
+
+static void BM_LogEventFilterSet(benchmark::State& state) {
+    while (state.KeepRunning()) {
+        LogEventFilterGeneric<std::set<int>> eventFilter;
+        // populate
+        eventFilter.setAtomIds(kAtomIdsSet, nullptr);
+        // many fetches
+        for (const auto& atomId : kSampleIdsList) {
+            benchmark::DoNotOptimize(eventFilter.isAtomInUse(atomId));
+        }
+    }
+}
+BENCHMARK(BM_LogEventFilterSet);
+
+static void BM_LogEventFilterSet2Consumers(benchmark::State& state) {
+    while (state.KeepRunning()) {
+        LogEventFilterGeneric<std::set<int>> eventFilter;
+        // populate
+        eventFilter.setAtomIds(kAtomIdsSet, &kAtomIdsSet);
+        eventFilter.setAtomIds(kAtomIdsSet2, &kAtomIdsSet2);
+        eventFilter.setAtomIds(kAtomIdsSet3, &kAtomIdsSet);
+        eventFilter.setAtomIds(kAtomIdsSet4, &kAtomIdsSet2);
+        // many fetches
+        for (const auto& atomId : kSampleIdsList) {
+            benchmark::DoNotOptimize(eventFilter.isAtomInUse(atomId));
+        }
+    }
+}
+BENCHMARK(BM_LogEventFilterSet2Consumers);
+
+}  //  namespace statsd
+}  //  namespace os
+}  //  namespace android
diff --git a/statsd/benchmark/metric_util.cpp b/statsd/benchmark/metric_util.cpp
index 89fd3d9..4f18298 100644
--- a/statsd/benchmark/metric_util.cpp
+++ b/statsd/benchmark/metric_util.cpp
@@ -354,10 +354,12 @@
     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; });
+    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; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {},
+            /*logEventFilter=*/nullptr);
     processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config);
     return processor;
 }
diff --git a/statsd/src/FieldValue.h b/statsd/src/FieldValue.h
index 5906942..286005a 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 {
diff --git a/statsd/src/StatsLogProcessor.cpp b/statsd/src/StatsLogProcessor.cpp
index 56d5f61..85f0008 100644
--- a/statsd/src/StatsLogProcessor.cpp
+++ b/statsd/src/StatsLogProcessor.cpp
@@ -20,6 +20,7 @@
 #include "StatsLogProcessor.h"
 
 #include <android-base/file.h>
+#include <android-modules-utils/sdk_level.h>
 #include <cutils/multiuser.h>
 #include <src/active_config_list.pb.h>
 #include <src/experiment_ids.pb.h>
@@ -39,6 +40,7 @@
 
 using namespace android;
 using android::base::StringPrintf;
+using android::modules::sdklevel::IsAtLeastU;
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
 using android::util::FIELD_TYPE_FLOAT;
@@ -53,6 +55,8 @@
 namespace os {
 namespace statsd {
 
+using aidl::android::os::IStatsQueryCallback;
+
 // for ConfigMetricsReportList
 const int FIELD_ID_CONFIG_KEY = 1;
 const int FIELD_ID_REPORTS = 2;
@@ -84,25 +88,32 @@
 // Cool down period for writing data to disk to avoid overwriting files.
 #define WRITE_DATA_COOL_DOWN_SEC 15
 
-StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
-                                     const sp<StatsPullerManager>& pullerManager,
-                                     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)
-    : mUidMap(uidMap),
+StatsLogProcessor::StatsLogProcessor(
+        const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
+        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)
+    : 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) {
     mPullerManager->ForceClearPullerCache();
     StateManager::getInstance().updateLogSources(uidMap);
+    // It is safe called locked version at constructor - no concurrent access possible
+    updateLogEventFilterLocked();
 }
 
 StatsLogProcessor::~StatsLogProcessor() {
@@ -384,8 +395,9 @@
 
     // Tell StatsdStats about new event
     const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs();
-    int atomId = event->GetTagId();
-    StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC);
+    const int atomId = event->GetTagId();
+    StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC,
+                                              event->isParsedHeaderOnly());
     if (!event->isValid()) {
         StatsdStats::getInstance().noteAtomError(atomId);
         return;
@@ -437,16 +449,24 @@
         informAnomalyAlarmFiredLocked(NanoToMillis(elapsedRealtimeNs));
     }
 
-    int64_t curTimeSec = getElapsedRealtimeSec();
+    const int64_t curTimeSec = getElapsedRealtimeSec();
     if (curTimeSec - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) {
         mPullerManager->ClearPullerCacheIfNecessary(curTimeSec * NS_PER_SEC);
         mLastPullerCacheClearTimeSec = curTimeSec;
     }
 
+    flushRestrictedDataIfNecessaryLocked(elapsedRealtimeNs);
+    enforceDataTtlsIfNecessaryLocked(getWallClockNs(), elapsedRealtimeNs);
+    enforceDbGuardrailsIfNecessaryLocked(getWallClockNs(), elapsedRealtimeNs);
+
     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();
@@ -530,9 +550,21 @@
 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()) {
+            // 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,
@@ -540,8 +572,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");
         }
@@ -549,16 +596,34 @@
         // 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(), {});
+            dbutils::deleteDb(key);
+        }
         mMetricsManagers.erase(key);
+        mUidMap->OnConfigRemoved(key);
     }
+
+    updateLogEventFilterLocked();
 }
 
 size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) const {
@@ -595,6 +660,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());
@@ -603,7 +674,6 @@
     // End of ConfigKey.
 
     bool keepFile = false;
-    auto it = mMetricsManagers.find(key);
     if (it != mMetricsManagers.end() && it->second->shouldPersistLocalHistory()) {
         keepFile = true;
     }
@@ -677,6 +747,12 @@
     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();
 
@@ -764,6 +840,10 @@
     if (it != mMetricsManagers.end()) {
         WriteDataToDiskLocked(key, getElapsedRealtimeNs(), getWallClockNs(), CONFIG_REMOVED,
                               NO_TIME_CONSTRAINTS);
+        if (IsAtLeastU() && it->second->hasRestrictedMetricsDelegate()) {
+            dbutils::deleteDb(key);
+            mSendRestrictedMetricsBroadcast(key, it->second->getRestrictedMetricsDelegate(), {});
+        }
         mMetricsManagers.erase(it);
         mUidMap->OnConfigRemoved(key);
     }
@@ -786,6 +866,221 @@
     if (mMetricsManagers.empty()) {
         mPullerManager->ForceClearPullerCache();
     }
+
+    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,
@@ -800,22 +1095,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
+                                           : StatsdStats::kBytesPerConfigTriggerGetData;
     bool requestDump = false;
     if (totalBytes > StatsdStats::kMaxMetricsBytesPerConfig) {
         // 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()) {
@@ -842,6 +1146,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 */,
@@ -1158,6 +1466,31 @@
     }
 }
 
+LogEventFilter::AtomIdSet StatsLogProcessor::getDefaultAtomIdSet() {
+    // populate hard-coded list of useful atoms
+    // 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};
+    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);
+    }
+    StateManager::getInstance().addAllAtomIds(allAtomIds);
+    VLOG("StatsLogProcessor: Updating allAtomIds done. Total atoms %d", (int)allAtomIds.size());
+    mLogEventFilter->setAtomIds(std::move(allAtomIds), this);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/StatsLogProcessor.h b/statsd/src/StatsLogProcessor.h
index 6681763..4841263 100644
--- a/statsd/src/StatsLogProcessor.h
+++ b/statsd/src/StatsLogProcessor.h
@@ -16,33 +16,37 @@
 
 #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 "external/StatsPullerManager.h"
-
+#include "socket/LogEventFilter.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 std::function<bool(const ConfigKey&)>& sendBroadcast,
-                      const std::function<bool(const int&,
-                                               const vector<int64_t>&)>& sendActivationBroadcast);
+    StatsLogProcessor(
+            const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerManager,
+            const sp<AlarmMonitor>& anomalyAlarmMonitor,
+            const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor, const 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);
@@ -108,6 +112,9 @@
                           int64_t currentWallClockTimeNs,
                           int64_t systemElapsedTimeNs);
 
+    /* Enforces ttls for restricted metrics */
+    void EnforceDataTtls(const int64_t wallClockNs, const 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);
 
@@ -142,6 +149,12 @@
     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);
+        }
     }
 
     // Add a specific config key to the possible configs to dump ASAP.
@@ -151,6 +164,17 @@
 
     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();
+
 private:
     // For testing only.
     inline sp<AlarmMonitor> getAnomalyAlarmMonitor() const {
@@ -178,6 +202,15 @@
     // Tracks when we last checked the bytes consumed for each config key.
     std::unordered_map<ConfigKey, int64_t> mLastByteSizeTimes;
 
+    // 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;
 
@@ -189,6 +222,8 @@
 
     sp<AlarmMonitor> mPeriodicAlarmMonitor;
 
+    std::shared_ptr<LogEventFilter> mLogEventFilter;
+
     void OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs);
 
     void resetIfConfigTtlExpiredLocked(const int64_t eventTimeNs);
@@ -228,10 +263,28 @@
              (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, const 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(const 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;
 
@@ -268,6 +321,13 @@
             const 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;
+
     // 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;
@@ -276,6 +336,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.
@@ -299,6 +365,7 @@
 
     bool mPrintAllLogs = false;
 
+    friend class StatsLogProcessorTestRestricted;
     FRIEND_TEST(StatsLogProcessorTest, TestOutOfOrderLogs);
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
@@ -310,7 +377,22 @@
     FRIEND_TEST(StatsLogProcessorTest,
             TestActivationOnBootMultipleActivationsDifferentActivationTypes);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
-
+    FRIEND_TEST(StatsLogProcessorTest, LogEventFilterOnSetPrintLogs);
+    FRIEND_TEST(StatsLogProcessorTest, TestUidMapHasSnapshot);
+    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);
@@ -330,6 +412,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);
@@ -389,6 +484,8 @@
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithValueFieldPositionALL);
 
     FRIEND_TEST(KllMetricE2eTest, TestInitWithKllFieldPositionALL);
+
+    FRIEND_TEST(StatsServiceStatsdInitTest, StatsServiceStatsdInitTest);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/StatsService.cpp b/statsd/src/StatsService.cpp
index 302b81b..c34964d 100644
--- a/statsd/src/StatsService.cpp
+++ b/statsd/src/StatsService.cpp
@@ -18,20 +18,15 @@
 #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-modules-utils/sdk_level.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>
@@ -39,9 +34,20 @@
 #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"
+
 using namespace android;
 
 using android::base::StringPrintf;
+using android::modules::sdklevel::IsAtLeastU;
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_MESSAGE;
 
@@ -53,6 +59,8 @@
 
 constexpr const char* kPermissionDump = "android.permission.DUMP";
 
+constexpr const char* kTracedProbesSid = "u:r:traced_probes:s0";
+
 constexpr const char* kPermissionRegisterPullAtom = "android.permission.REGISTER_STATS_PULL_ATOM";
 
 #define STATS_SERVICE_DIR "/data/misc/stats-service"
@@ -88,7 +96,37 @@
     }                                                             \
 }
 
-StatsService::StatsService(const sp<UidMap>& uidMap, shared_ptr<LogEventQueue> queue)
+Status checkSid(const char* expectedSid) {
+    const char* sid = nullptr;
+    if (__builtin_available(android __ANDROID_API_U__, *)) {
+        sid = AIBinder_getCallingSid();
+    }
+
+    // root (which is the uid in tests for example) has all permissions.
+    uid_t uid = AIBinder_getCallingUid();
+    if (uid == AID_ROOT) {
+        return Status::ok();
+    }
+
+    if (sid != nullptr && strcmp(expectedSid, sid) == 0) {
+        return Status::ok();
+    } else {
+        return exception(EX_SECURITY,
+                         StringPrintf("SID '%s' is not expected SID '%s'", sid, expectedSid));
+    }
+}
+
+#define ENFORCE_SID(sid)                 \
+    {                                    \
+        Status status = checkSid((sid)); \
+        if (!status.isOk()) {            \
+            return status;               \
+        }                                \
+    }
+
+StatsService::StatsService(const sp<UidMap>& uidMap, shared_ptr<LogEventQueue> queue,
+                           const std::shared_ptr<LogEventFilter>& logEventFilter,
+                           int initEventDelaySecs)
     : mUidMap(uidMap),
       mAnomalyAlarmMonitor(new AlarmMonitor(
               MIN_DIFF_TO_UPDATE_REGISTERED_ALARM_SECS,
@@ -115,10 +153,12 @@
                   }
               })),
       mEventQueue(std::move(queue)),
+      mLogEventFilter(logEventFilter),
       mBootCompleteTrigger({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag},
-                           [this]() { mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs()); }),
+                           [this]() { onStatsdInitCompleted(); }),
       mStatsCompanionServiceDeathRecipient(
-              AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)) {
+              AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)),
+      mInitEventDelaySecs(initEventDelaySecs) {
     mPullerManager = new StatsPullerManager();
     StatsPuller::SetUidMap(mUidMap);
     mConfigManager = new ConfigManager();
@@ -160,7 +200,29 @@
                 }
                 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);
     mConfigManager->AddListener(mProcessor);
@@ -365,12 +427,7 @@
         }
 
         if (!utf8Args[0].compare(String8("data-subscribe"))) {
-            {
-                std::lock_guard<std::mutex> lock(mShellSubscriberMutex);
-                if (mShellSubscriber == nullptr) {
-                    mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager);
-                }
-            }
+            initShellSubscriber();
             int timeoutSec = -1;
             if (argc >= 2) {
                 timeoutSec = atoi(utf8Args[1].c_str());
@@ -1086,10 +1143,28 @@
     return Status::ok();
 }
 
+void StatsService::onStatsdInitCompleted() {
+    if (mInitEventDelaySecs > 0) {
+        // The hard-coded delay is determined based on perfetto traces evaluation
+        // for statsd during the boot.
+        // The delay is required to properly process event storm which often has place
+        // after device boot.
+        // This function is called from a dedicated thread without holding locks, so sleeping is ok.
+        // See MultiConditionTrigger::markComplete() executorThread for details
+        // For more details see http://b/277958338
+        std::this_thread::sleep_for(std::chrono::seconds(mInitEventDelaySecs));
+    }
+
+    mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs());
+}
+
 void StatsService::Startup() {
     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() {
@@ -1314,11 +1389,8 @@
 Status StatsService::updateProperties(const vector<PropertyParcel>& properties) {
     ENFORCE_UID(AID_SYSTEM);
 
-    for (const auto& [property, value] : properties) {
-        if (property == kIncludeCertificateHash) {
-            mUidMap->setIncludeCertificateHash(value == "true");
-        }
-    }
+    // TODO(b/281765292): Forward statsd_java properties received here to FlagProvider.
+
     return Status::ok();
 }
 
@@ -1356,6 +1428,90 @@
     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) {
+    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) {
+    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) {
+    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) {
+    ENFORCE_SID(kTracedProbesSid);
+
+    initShellSubscriber();
+
+    mShellSubscriber->startNewSubscription(subscriptionConfig, callback);
+    return Status::ok();
+}
+
+Status StatsService::removeSubscription(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    ENFORCE_SID(kTracedProbesSid);
+
+    if (mShellSubscriber != nullptr) {
+        mShellSubscriber->unsubscribe(callback);
+    }
+    return Status::ok();
+}
+
+Status StatsService::flushSubscription(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    ENFORCE_SID(kTracedProbesSid);
+
+    if (mShellSubscriber != nullptr) {
+        mShellSubscriber->flushSubscription(callback);
+    }
+    return Status::ok();
+}
+
+void StatsService::initShellSubscriber() {
+    std::lock_guard<std::mutex> lock(mShellSubscriberMutex);
+    if (mShellSubscriber == nullptr) {
+        mShellSubscriber = new ShellSubscriber(mUidMap, mPullerManager, mLogEventFilter);
+    }
+}
+
 void StatsService::stopReadingLogs() {
     mIsStopRequested = true;
     // Push this event so that readLogs will process and break out of the loop
diff --git a/statsd/src/StatsService.h b/statsd/src/StatsService.h
index f8fe03c..da1b4ab 100644
--- a/statsd/src/StatsService.h
+++ b/statsd/src/StatsService.h
@@ -20,6 +20,7 @@
 #include <aidl/android/os/BnStatsd.h>
 #include <aidl/android/os/IPendingIntentRef.h>
 #include <aidl/android/os/IPullAtomCallback.h>
+#include <aidl/android/os/IStatsSubscriptionCallback.h>
 #include <aidl/android/util/PropertyParcel.h>
 #include <gtest/gtest_prod.h>
 #include <utils/Looper.h>
@@ -40,10 +41,13 @@
 using namespace android::os;
 using namespace std;
 
+using ::ndk::SpAIBinder;
 using Status = ::ndk::ScopedAStatus;
 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;
 using ::ndk::ScopedFileDescriptor;
@@ -52,11 +56,11 @@
 namespace os {
 namespace statsd {
 
-constexpr const char* kIncludeCertificateHash = "include_certificate_hash";
-
 class StatsService : public BnStatsd {
 public:
-    StatsService(const sp<UidMap>& uidMap, shared_ptr<LogEventQueue> queue);
+    StatsService(const sp<UidMap>& uidMap, shared_ptr<LogEventQueue> queue,
+                 const std::shared_ptr<LogEventFilter>& logEventFilter,
+                 int initEventDelaySecs = kStatsdInitDelaySecs);
     virtual ~StatsService();
 
     /** The anomaly alarm registered with AlarmManager won't be updated by less than this. */
@@ -206,6 +210,53 @@
      */
     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,
+                                   const shared_ptr<IStatsSubscriptionCallback>& callback) override;
+
+    /**
+     * Binder call to remove a subscription.
+     */
+    virtual Status removeSubscription(
+            const shared_ptr<IStatsSubscriptionCallback>& callback) override;
+
+    /**
+     * Binder call to flush atom events for a subscription.
+     */
+    virtual Status flushSubscription(
+            const shared_ptr<IStatsSubscriptionCallback>& callback) override;
+
+    const static int kStatsdInitDelaySecs = 90;
+
 private:
     /**
      * Load system properties at init.
@@ -348,6 +399,16 @@
     void statsCompanionServiceDiedImpl();
 
     /**
+     * Initialize ShellSubscriber
+     */
+    void initShellSubscriber();
+
+    /*
+     * Notify StatsLogProcessor of boot completed
+     */
+    void onStatsdInitCompleted();
+
+    /**
      *  This method is used to stop log reader thread.
      */
     void stopReadingLogs();
@@ -396,6 +457,7 @@
      */
     mutable mutex mShellSubscriberMutex;
     shared_ptr<LogEventQueue> mEventQueue;
+    std::shared_ptr<LogEventFilter> mLogEventFilter;
 
     std::unique_ptr<std::thread> mLogsReaderThread;
 
@@ -406,7 +468,11 @@
 
     ScopedAIBinder_DeathRecipient mStatsCompanionServiceDeathRecipient;
 
+    const int mInitEventDelaySecs;
+
     friend class StatsServiceConfigTest;
+    friend class StatsServiceStatsdInitTest;
+    friend class RestrictedConfigE2ETest;
 
     FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
     FRIEND_TEST(StatsServiceTest, TestAddConfig_simple);
@@ -426,13 +492,18 @@
     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);
     FRIEND_TEST(AnomalyDurationDetectionE2eTest, TestDurationMetric_SUM_partial_bucket);
     FRIEND_TEST(AnomalyDurationDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
     FRIEND_TEST(AnomalyDurationDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
+
+    FRIEND_TEST(StatsServiceStatsdInitTest, StatsServiceStatsdInitTest);
 };
 
 }  // namespace statsd
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/config/ConfigKeyWithPackage.h b/statsd/src/config/ConfigKeyWithPackage.h
new file mode 100644
index 0000000..85e95d5
--- /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, const 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/ConfigManager.cpp b/statsd/src/config/ConfigManager.cpp
index 570cd45..16c4e75 100644
--- a/statsd/src/config/ConfigManager.cpp
+++ b/statsd/src/config/ConfigManager.cpp
@@ -37,6 +37,8 @@
 using std::string;
 using std::vector;
 
+using Status = ::ndk::ScopedAStatus;
+
 #define STATS_SERVICE_DIR "/data/misc/stats-service"
 
 using android::base::StringPrintf;
@@ -152,6 +154,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 +260,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..dd7d5bb 100644
--- a/statsd/src/config/ConfigManager.h
+++ b/statsd/src/config/ConfigManager.h
@@ -16,14 +16,15 @@
 
 #pragma once
 
-#include "config/ConfigKey.h"
-#include "config/ConfigListener.h"
-
 #include <aidl/android/os/IPendingIntentRef.h>
+#include <stdio.h>
+
 #include <mutex>
 #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 +114,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, const 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, const 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 +188,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/external/StatsPullerManager.cpp b/statsd/src/external/StatsPullerManager.cpp
index ca5ee5b..8502d4f 100644
--- a/statsd/src/external/StatsPullerManager.cpp
+++ b/statsd/src/external/StatsPullerManager.cpp
@@ -32,7 +32,6 @@
 #include "../statscompanion_util.h"
 #include "StatsCallbackPuller.h"
 #include "TrainInfoPuller.h"
-#include "flags/FlagProvider.h"
 #include "statslog_statsd.h"
 
 using std::shared_ptr;
@@ -50,8 +49,7 @@
               // TrainInfo.
               {{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()},
       }),
-      mNextPullTimeNs(NO_ALARM_UPDATE),
-      mLimitPull(FlagProvider::getInstance().getBootFlagBool(LIMIT_PULL_FLAG, FLAG_FALSE)) {
+      mNextPullTimeNs(NO_ALARM_UPDATE) {
 }
 
 bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
@@ -229,33 +227,22 @@
         vector<ReceiverInfo*> receivers;
         if (pair.second.size() != 0) {
             for (ReceiverInfo& receiverInfo : pair.second) {
-                // If mLimitPull is true, check if metric needs to pull data (pullNecessary).
                 // If pullNecessary and enough time has passed for the next bucket, then add
                 // receiver to the list that will pull on this alarm.
                 // If pullNecessary is false, check if next pull time needs to be updated.
-                if (mLimitPull) {
-                    sp<PullDataReceiver> receiverPtr = receiverInfo.receiver.promote();
-                    const bool pullNecessary =
-                            receiverPtr != nullptr && receiverPtr->isPullNeeded();
-                    if (receiverInfo.nextPullTimeNs <= elapsedTimeNs && pullNecessary) {
-                        receivers.push_back(&receiverInfo);
-                    } else {
-                        if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) {
-                            receiverPtr->onDataPulled({}, PullResult::PULL_NOT_NEEDED,
-                                                      elapsedTimeNs);
-                            int numBucketsAhead = (elapsedTimeNs - receiverInfo.nextPullTimeNs) /
-                                                  receiverInfo.intervalNs;
-                            receiverInfo.nextPullTimeNs +=
-                                    (numBucketsAhead + 1) * receiverInfo.intervalNs;
-                        }
-                        minNextPullTimeNs = min(receiverInfo.nextPullTimeNs, minNextPullTimeNs);
-                    }
+                sp<PullDataReceiver> receiverPtr = receiverInfo.receiver.promote();
+                const bool pullNecessary = receiverPtr != nullptr && receiverPtr->isPullNeeded();
+                if (receiverInfo.nextPullTimeNs <= elapsedTimeNs && pullNecessary) {
+                    receivers.push_back(&receiverInfo);
                 } else {
                     if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) {
-                        receivers.push_back(&receiverInfo);
-                    } else {
-                        minNextPullTimeNs = min(receiverInfo.nextPullTimeNs, minNextPullTimeNs);
+                        receiverPtr->onDataPulled({}, PullResult::PULL_NOT_NEEDED, elapsedTimeNs);
+                        int numBucketsAhead = (elapsedTimeNs - receiverInfo.nextPullTimeNs) /
+                                              receiverInfo.intervalNs;
+                        receiverInfo.nextPullTimeNs +=
+                                (numBucketsAhead + 1) * receiverInfo.intervalNs;
                     }
+                    minNextPullTimeNs = min(receiverInfo.nextPullTimeNs, minNextPullTimeNs);
                 }
             }
             if (receivers.size() > 0) {
diff --git a/statsd/src/external/StatsPullerManager.h b/statsd/src/external/StatsPullerManager.h
index f9db4c4..80a1331 100644
--- a/statsd/src/external/StatsPullerManager.h
+++ b/statsd/src/external/StatsPullerManager.h
@@ -164,8 +164,6 @@
 
     int64_t mNextPullTimeNs;
 
-    const bool mLimitPull;
-
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTrigger);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTriggerWithActivation);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvents);
diff --git a/statsd/src/flags/FlagProvider.h b/statsd/src/flags/FlagProvider.h
index 7e11feb..8f24ac9 100644
--- a/statsd/src/flags/FlagProvider.h
+++ b/statsd/src/flags/FlagProvider.h
@@ -37,7 +37,8 @@
 const std::string STATSD_NATIVE_NAMESPACE = "statsd_native";
 const std::string STATSD_NATIVE_BOOT_NAMESPACE = "statsd_native_boot";
 
-const std::string LIMIT_PULL_FLAG = "limit_pull";
+const std::string OPTIMIZATION_SOCKET_PARSING_FLAG = "optimization_socket_parsing";
+const std::string STATSD_INIT_COMPLETED_NO_DELAY_FLAG = "statsd_init_completed_no_delay";
 
 const std::string FLAG_TRUE = "true";
 const std::string FLAG_FALSE = "false";
@@ -113,9 +114,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);
@@ -127,6 +134,15 @@
     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(LogEventTest, TestRestrictionCategoryAnnotationFlagDisabled);
+    FRIEND_TEST(LogEvent_FieldRestrictionTest, TestFieldRestrictionAnnotation);
+    FRIEND_TEST(LogEvent_FieldRestrictionTest, TestInvalidAnnotationIntType);
+    FRIEND_TEST(LogEvent_FieldRestrictionTest, TestInvalidAnnotationAtomLevel);
+    FRIEND_TEST(LogEvent_FieldRestrictionTest, TestRestrictionCategoryAnnotationFlagDisabled);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/guardrail/StatsdStats.cpp b/statsd/src/guardrail/StatsdStats.cpp
index 4ed0089..7dd6d9a 100644
--- a/statsd/src/guardrail/StatsdStats.cpp
+++ b/statsd/src/guardrail/StatsdStats.cpp
@@ -19,9 +19,11 @@
 #include "StatsdStats.h"
 
 #include <android/util/ProtoOutputStream.h>
+
 #include "../stats_log_util.h"
 #include "statslog_statsd.h"
 #include "storage/StorageManager.h"
+#include "utils/ShardOffsetProvider.h"
 
 namespace android {
 namespace os {
@@ -35,6 +37,7 @@
 using android::util::FIELD_TYPE_INT64;
 using android::util::FIELD_TYPE_MESSAGE;
 using android::util::FIELD_TYPE_STRING;
+using android::util::FIELD_TYPE_UINT32;
 using android::util::ProtoOutputStream;
 using std::lock_guard;
 using std::shared_ptr;
@@ -53,11 +56,24 @@
 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_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;
 const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3;
 const int FIELD_ID_ATOM_STATS_DROPS_COUNT = 4;
+const int FIELD_ID_ATOM_STATS_SKIP_COUNT = 5;
 
 const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1;
 const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1;
@@ -99,6 +115,12 @@
 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_INVALID_CONFIG_REASON_ENUM = 1;
 const int FIELD_ID_INVALID_CONFIG_REASON_METRIC_ID = 2;
@@ -126,6 +148,14 @@
 const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_UID = 1;
 const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL_TIME = 2;
 
+// 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 std::map<int, std::pair<size_t, size_t>> StatsdStats::kAtomDimensionKeySizeLimitMap = {
         {util::BINDER_CALLS, {6000, 10000}},
         {util::LOOPER_STATS, {1500, 2500}},
@@ -271,7 +301,8 @@
     noteDataDropped(key, totalBytes, getWallClockSec());
 }
 
-void StatsdStats::noteEventQueueOverflow(int64_t oldestEventTimestampNs, int32_t atomId) {
+void StatsdStats::noteEventQueueOverflow(int64_t oldestEventTimestampNs, int32_t atomId,
+                                         bool isSkipped) {
     lock_guard<std::mutex> lock(mLock);
 
     mOverflowCount++;
@@ -286,7 +317,7 @@
         mMinQueueHistoryNs = history;
     }
 
-    noteAtomLoggedLocked(atomId);
+    noteAtomLoggedLocked(atomId, isSkipped);
     noteAtomDroppedLocked(atomId);
 }
 
@@ -331,6 +362,26 @@
     it->second->dump_report_stats.push_back(std::make_pair(timeSec, num_bytes));
 }
 
+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::noteUidMapDropped(int deltas) {
     lock_guard<std::mutex> lock(mLock);
     mUidMapStats.dropped_changes += mUidMapStats.dropped_changes + deltas;
@@ -480,22 +531,24 @@
     mPulledAtomStats[pullAtomId].pullExceedMaxDelay++;
 }
 
-void StatsdStats::noteAtomLogged(int atomId, int32_t /*timeSec*/) {
+void StatsdStats::noteAtomLogged(int atomId, int32_t /*timeSec*/, bool isSkipped) {
     lock_guard<std::mutex> lock(mLock);
 
-    noteAtomLoggedLocked(atomId);
+    noteAtomLoggedLocked(atomId, isSkipped);
 }
 
-void StatsdStats::noteAtomLoggedLocked(int atomId) {
+void StatsdStats::noteAtomLoggedLocked(int atomId, bool isSkipped) {
     if (atomId >= 0 && atomId <= kMaxPushedAtomId) {
-        mPushedAtomStats[atomId]++;
+        mPushedAtomStats[atomId].logCount++;
+        mPushedAtomStats[atomId].skipCount += isSkipped;
     } else {
         if (atomId < 0) {
             android_errorWriteLog(0x534e4554, "187957589");
         }
         if (mNonPlatformPushedAtomStats.size() < kMaxNonPlatformPushedAtoms ||
             mNonPlatformPushedAtomStats.find(atomId) != mNonPlatformPushedAtomStats.end()) {
-            mNonPlatformPushedAtomStats[atomId]++;
+            mNonPlatformPushedAtomStats[atomId].logCount++;
+            mNonPlatformPushedAtomStats[atomId].skipCount += isSkipped;
         }
     }
 }
@@ -611,6 +664,137 @@
     }
 }
 
+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++;
+}
+
 StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) {
     auto atomMetricStatsIter = mAtomMetricStats.find(metricId);
     if (atomMetricStatsIter != mAtomMetricStats.end()) {
@@ -629,7 +813,7 @@
     // Reset the historical data, but keep the active ConfigStats
     mStartTimeSec = getWallClockSec();
     mIceBox.clear();
-    std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), 0);
+    std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), PushedAtomStats());
     mNonPlatformPushedAtomStats.clear();
     mAnomalyAlarmRegisteredStats = 0;
     mPeriodicAlarmRegisteredStats = 0;
@@ -651,6 +835,11 @@
         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();
     }
     for (auto& pullStats : mPulledAtomStats) {
         pullStats.second.totalPull = 0;
@@ -678,6 +867,7 @@
     mActivationBroadcastGuardrailStats.clear();
     mPushedAtomErrorStats.clear();
     mPushedAtomDropsStats.clear();
+    mRestrictedMetricQueryStats.clear();
 }
 
 string buildTimeString(int64_t timeSec) {
@@ -717,11 +907,13 @@
     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, device_info_table_creation_failed=%d, "
+                "db_corrupted_count=%d\n",
                 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);
+                configStats->alert_count, configStats->is_valid,
+                configStats->device_info_table_creation_failed, configStats->db_corrupted_count);
 
         if (!configStats->is_valid) {
             dprintf(out, "\tinvalid config reason: %s\n",
@@ -747,17 +939,27 @@
             dprintf(out, "\tdata drop time: %d with size %lld", *dropTimePtr,
                     (long long)*dropBytesPtr);
         }
+
+        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, device_info_table_creation_failed=%d, "
+                "db_corrupted_count=%d\n",
                 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);
+                configStats->is_valid, configStats->device_info_table_creation_failed,
+                configStats->db_corrupted_count);
 
         if (!configStats->is_valid) {
             dprintf(out, "\tinvalid config reason: %s\n",
@@ -814,22 +1016,43 @@
         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);
+        }
     }
     dprintf(out, "********Disk Usage stats***********\n");
     StorageManager::printStats(out);
     dprintf(out, "********Pushed Atom stats***********\n");
     const size_t atomCounts = mPushedAtomStats.size();
     for (size_t i = 2; i < atomCounts; i++) {
-        if (mPushedAtomStats[i] > 0) {
-            dprintf(out, "Atom %zu->(total count)%d, (error count)%d, (drop count)%d\n", i,
-                    mPushedAtomStats[i], getPushedAtomErrorsLocked((int)i),
-                    getPushedAtomDropsLocked((int)i));
+        if (mPushedAtomStats[i].logCount > 0) {
+            dprintf(out,
+                    "Atom %zu->(total count)%d, (error count)%d, (drop count)%d, (skip count)%d\n",
+                    i, mPushedAtomStats[i].logCount, getPushedAtomErrorsLocked((int)i),
+                    getPushedAtomDropsLocked((int)i), mPushedAtomStats[i].skipCount);
         }
     }
     for (const auto& pair : mNonPlatformPushedAtomStats) {
-        dprintf(out, "Atom %d->(total count)%d, (error count)%d, (drop count)%d\n", pair.first,
-                pair.second, getPushedAtomErrorsLocked(pair.first),
-                getPushedAtomDropsLocked((int)pair.first));
+        dprintf(out, "Atom %d->(total count)%d, (error count)%d, (drop count)%d, (skip count)%d\n",
+                pair.first, pair.second.logCount, getPushedAtomErrorsLocked(pair.first),
+                getPushedAtomDropsLocked((int)pair.first), pair.second.skipCount);
     }
 
     dprintf(out, "********Pulled Atom stats***********\n");
@@ -856,7 +1079,7 @@
             string uptimeMillis = "(pull timeout system uptime millis) ";
             string pullTimeoutMillis = "(pull timeout elapsed time millis) ";
             for (const auto& stats : pair.second.pullTimeoutMetadata) {
-                uptimeMillis.append(to_string(stats.pullTimeoutUptimeMillis)).append(",");;
+                uptimeMillis.append(to_string(stats.pullTimeoutUptimeMillis)).append(",");
                 pullTimeoutMillis.append(to_string(stats.pullTimeoutElapsedMillis)).append(",");
             }
             uptimeMillis.pop_back();
@@ -908,6 +1131,30 @@
         }
         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, "********Shard Offset Provider stats***********\n");
+    dprintf(out, "Shard Offset: %u\n", ShardOffsetProvider::getInstance().getShardOffset());
 }
 
 void addConfigStatsToProto(const ConfigStats& configStats, ProtoOutputStream* proto) {
@@ -1054,6 +1301,47 @@
         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);
+    }
+    proto->write(FIELD_TYPE_BOOL | FIELD_ID_CONFIG_STATS_DEVICE_INFO_TABLE_CREATION_FAILED,
+                 configStats.device_info_table_creation_failed);
+    proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_RESTRICTED_DB_CORRUPTED_COUNT,
+                 configStats.db_corrupted_count);
+    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);
 }
 
@@ -1074,17 +1362,19 @@
 
     const size_t atomCounts = mPushedAtomStats.size();
     for (size_t i = 2; i < atomCounts; i++) {
-        if (mPushedAtomStats[i] > 0) {
+        if (mPushedAtomStats[i].logCount > 0) {
             uint64_t token =
                     proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
             proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i);
-            proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]);
+            proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i].logCount);
             const int errors = getPushedAtomErrorsLocked(i);
             writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors,
                                      &proto);
             const int drops = getPushedAtomDropsLocked(i);
             writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_DROPS_COUNT, drops,
                                      &proto);
+            writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_SKIP_COUNT,
+                                     mPushedAtomStats[i].skipCount, &proto);
             proto.end(token);
         }
     }
@@ -1093,12 +1383,14 @@
         uint64_t token =
                 proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
         proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first);
-        proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second);
+        proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second.logCount);
         const int errors = getPushedAtomErrorsLocked(pair.first);
         writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors,
                                  &proto);
         const int drops = getPushedAtomDropsLocked(pair.first);
         writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_DROPS_COUNT, drops, &proto);
+        writeNonZeroStatToStream(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_SKIP_COUNT,
+                                 pair.second.skipCount, &proto);
         proto.end(token);
     }
 
@@ -1172,6 +1464,42 @@
         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()));
+
     output->clear();
     size_t bufferSize = proto.size();
     output->resize(bufferSize);
diff --git a/statsd/src/guardrail/StatsdStats.h b/statsd/src/guardrail/StatsdStats.h
index a221d55..4bf6ffd 100644
--- a/statsd/src/guardrail/StatsdStats.h
+++ b/statsd/src/guardrail/StatsdStats.h
@@ -52,6 +52,27 @@
     }
 };
 
+// Keep this in sync with InvalidQueryReason enum in stats_log.proto
+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
+};
+
+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 ConfigStats {
     int32_t uid;
     int64_t id;
@@ -63,6 +84,8 @@
     int32_t matcher_count;
     int32_t alert_count;
     bool is_valid;
+    bool device_info_table_creation_failed = false;
+    int32_t db_corrupted_count = 0;
 
     // Stores reasons for why config is valid or not
     std::optional<InvalidConfigReason> reason;
@@ -105,6 +128,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 {
@@ -146,6 +180,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 kMaxMetricsBytesPerConfig = 2 * 1024 * 1024;
@@ -154,6 +196,10 @@
     // data subscriber that it's time to call getData.
     static const size_t kBytesPerConfigTriggerGetData = 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.
     const static size_t kMaxBytesUsedUidMap = 50 * 1024;
@@ -167,6 +213,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;
 
@@ -191,9 +246,12 @@
     // Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId.
     static const int kMaxNonPlatformPushedAtoms = 600;
 
+    // Maximum number of pushed atoms error statsd stats will track.
+    static const int kMaxPushedAtomErrorStatsSize = 100;
+
     // 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 = 750;
+    static const int kMaxPushedAtomId = 900;
 
     // Atom id that is the start of the pulled atoms.
     static const int kPullAtomStartTag = 10000;
@@ -261,6 +319,16 @@
     void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes);
 
     /**
+     * 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 the size of output tuple of a condition.
      *
      * Note: only report when the condition has an output dimension, and the tuple
@@ -315,7 +383,7 @@
     /**
      * Report an atom event has been logged.
      */
-    void noteAtomLogged(int atomId, int32_t timeSec);
+    void noteAtomLogged(int atomId, int32_t timeSec, bool isSkipped);
 
     /**
      * Report that statsd modified the anomaly alarm registered with StatsCompanionService.
@@ -487,23 +555,63 @@
 
     /* Reports one event id has been dropped due to queue overflow, and the oldest event timestamp
      * in the queue */
-    void noteEventQueueOverflow(int64_t oldestEventTimestampNs, int32_t atomId);
+    void noteEventQueueOverflow(int64_t oldestEventTimestampNs, int32_t atomId, bool isSkipped);
 
     /**
      * 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.
      */
-     void noteActivationBroadcastGuardrailHit(const int uid);
+    void noteActivationBroadcastGuardrailHit(const int uid);
 
-     /**
-      * Reports that an atom is erroneous or cannot be parsed successfully by
-      * statsd. An atom tag of 0 indicates that the client did not supply the
-      * atom id within the encoding.
-      *
-      * For pushed atoms only, this call should be preceded by a call to
-      * noteAtomLogged.
-      */
-     void noteAtomError(int atomTag, bool pull=false);
+    /**
+     * Reports that an atom is erroneous or cannot be parsed successfully by
+     * statsd. An atom tag of 0 indicates that the client did not supply the
+     * atom id within the encoding.
+     *
+     * For pushed atoms only, this call should be preceded by a call to
+     * noteAtomLogged.
+     */
+    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, const 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, const int64_t metricId);
+
+    // Reports that a restricted metric fails to delete table in database.
+    void noteRestrictedMetricTableDeletionError(const ConfigKey& configKey, const int64_t metricId);
+
+    // Reports the time it takes for a restricted metric to flush the data to the database.
+    void noteRestrictedMetricFlushLatency(const ConfigKey& configKey, const int64_t metricId,
+                                          const int64_t flushLatencyNs);
+
+    // Reports that a restricted metric had a category change.
+    void noteRestrictedMetricCategoryChanged(const ConfigKey& configKey, const 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, const int64_t elapsedTimeNs,
+                                    const int64_t dbSize);
 
     /**
      * Reset the historical stats. Including all stats in icebox, and the tracked stats about
@@ -532,9 +640,10 @@
     typedef struct PullTimeoutMetadata {
         int64_t pullTimeoutUptimeMillis;
         int64_t pullTimeoutElapsedMillis;
-        PullTimeoutMetadata(int64_t uptimeMillis, int64_t elapsedMillis) :
-            pullTimeoutUptimeMillis(uptimeMillis),
-            pullTimeoutElapsedMillis(elapsedMillis) {/* do nothing */}
+        PullTimeoutMetadata(int64_t uptimeMillis, int64_t elapsedMillis)
+            : pullTimeoutUptimeMillis(uptimeMillis),
+              pullTimeoutElapsedMillis(elapsedMillis) { /* do nothing */
+        }
     } PullTimeoutMetadata;
 
     typedef struct {
@@ -593,17 +702,23 @@
     // The size of the vector is capped by kMaxIceBoxSize.
     std::list<const std::shared_ptr<ConfigStats>> mIceBox;
 
-    // Stores the number of times a pushed atom is logged.
+    // 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
     // out of that range will be put in mNonPlatformPushedAtomStats.
     // This is a vector, not a map because it will be accessed A LOT -- for each stats log.
-    std::vector<int> mPushedAtomStats;
+    struct PushedAtomStats {
+        int logCount = 0;
+        int skipCount = 0;
+    };
 
-    // Stores the number of times a pushed atom is logged for atom ids above kMaxPushedAtomId.
-    // The max size of the map is kMaxNonPlatformPushedAtoms.
-    std::unordered_map<int, int> mNonPlatformPushedAtomStats;
+    std::vector<PushedAtomStats> mPushedAtomStats;
+
+    // Stores the number of times a pushed atom is logged and skipped for atom ids above
+    // kMaxPushedAtomId. The max size of the map is kMaxNonPlatformPushedAtoms.
+    std::unordered_map<int, PushedAtomStats> mNonPlatformPushedAtomStats;
 
     // Stores the number of times a pushed atom is dropped due to queue overflow event.
+    // We do not expect it will happen too often so the map is preferable vs pre-allocated vector
     // The max size of the map is kMaxPushedAtomId + kMaxNonPlatformPushedAtoms.
     std::unordered_map<int, int> mPushedAtomDropsStats;
 
@@ -612,9 +727,8 @@
 
     // Stores the number of times a pushed atom was logged erroneously. The
     // corresponding counts for pulled atoms are stored in PulledAtomStats.
-    // The max size of this map is kMaxAtomErrorsStatsSize.
+    // The max size of this map is kMaxPushedAtomErrorStatsSize.
     std::map<int, int> mPushedAtomErrorStats;
-    int kMaxPushedAtomErrorStatsSize = 100;
 
     // Maps metric ID to its stats. The size is capped by the number of metrics.
     std::map<int64_t, AtomMetricStats> mAtomMetricStats;
@@ -658,6 +772,40 @@
 
     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);
+
     // Stores the number of times statsd modified the anomaly alarm registered with
     // StatsCompanionService.
     int mAnomalyAlarmRegisteredStats = 0;
@@ -671,7 +819,7 @@
 
     void resetInternalLocked();
 
-    void noteAtomLoggedLocked(int atomId);
+    void noteAtomLoggedLocked(int atomId, bool isSkipped);
 
     void noteAtomDroppedLocked(int atomId);
 
@@ -712,8 +860,13 @@
     FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
     FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit);
     FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats);
+    FRIEND_TEST(StatsdStatsTest, TestAtomSkippedStats);
+    FRIEND_TEST(StatsdStatsTest, TestRestrictedMetricsStats);
+    FRIEND_TEST(StatsdStatsTest, TestRestrictedMetricsQueryStats);
     FRIEND_TEST(StatsdStatsTest, TestAtomDroppedStats);
-    FRIEND_TEST(StatsdStatsTest, TestAtomDroppedAndLoggedStats);
+    FRIEND_TEST(StatsdStatsTest, TestAtomLoggedAndDroppedStats);
+    FRIEND_TEST(StatsdStatsTest, TestAtomLoggedAndDroppedAndSkippedStats);
+    FRIEND_TEST(StatsdStatsTest, TestShardOffsetProvider);
 
     FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved);
 };
diff --git a/statsd/src/guardrail/invalid_config_reason_enum.proto b/statsd/src/guardrail/invalid_config_reason_enum.proto
index 7df4d3e..23a462d 100644
--- a/statsd/src/guardrail/invalid_config_reason_enum.proto
+++ b/statsd/src/guardrail/invalid_config_reason_enum.proto
@@ -106,4 +106,6 @@
     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;
 };
diff --git a/statsd/src/logd/LogEvent.cpp b/statsd/src/logd/LogEvent.cpp
index f112937..8cdd79c 100644
--- a/statsd/src/logd/LogEvent.cpp
+++ b/statsd/src/logd/LogEvent.cpp
@@ -20,10 +20,12 @@
 #include "logd/LogEvent.h"
 
 #include <android-base/stringprintf.h>
+#include <android-modules-utils/sdk_level.h>
 #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"
 
@@ -36,12 +38,25 @@
 
 using namespace android::util;
 using android::base::StringPrintf;
+using android::modules::sdklevel::IsAtLeastU;
 using android::util::ProtoOutputStream;
 using std::string;
 using std::vector;
 
+namespace {
+
+uint8_t getTypeId(uint8_t typeInfo) {
+    return typeInfo & 0x0F;  // type id in lower 4 bytes
+}
+
+uint8_t getNumAnnotations(uint8_t typeInfo) {
+    return (typeInfo >> 4) & 0x0F;  // num annotations in upper 4 bytes
+}
+
+}  // 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,
@@ -333,7 +348,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);
@@ -361,7 +376,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);
@@ -373,6 +388,38 @@
     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) {
+        // Only diagnostic is currently supported for use.
+        case ASTATSLOG_RESTRICTION_CATEGORY_DIAGNOSTIC:
+            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
@@ -383,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);
@@ -413,24 +483,34 @@
     }
 }
 
-uint8_t LogEvent::parseHeader() {
+LogEvent::BodyBufferInfo LogEvent::parseHeader(const uint8_t* buf, size_t len) {
+    BodyBufferInfo bodyInfo;
+
+    mParsedHeaderOnly = true;
+
+    mBuf = buf;
+    mRemainingLen = (uint32_t)len;
+
     // Beginning of buffer is OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID
     uint8_t typeInfo = readNextValue<uint8_t>();
     if (getTypeId(typeInfo) != OBJECT_TYPE) {
         mValid = false;
-        return 0;
+        mBuf = nullptr;
+        return bodyInfo;
     }
 
     uint8_t numElements = readNextValue<uint8_t>();
     if (numElements < 2 || numElements > INT8_MAX) {
         mValid = false;
-        return 0;
+        mBuf = nullptr;
+        return bodyInfo;
     }
 
     typeInfo = readNextValue<uint8_t>();
     if (getTypeId(typeInfo) != INT64_TYPE) {
         mValid = false;
-        return 0;
+        mBuf = nullptr;
+        return bodyInfo;
     }
     mElapsedTimestampNs = readNextValue<int64_t>();
     numElements--;
@@ -438,34 +518,33 @@
     typeInfo = readNextValue<uint8_t>();
     if (getTypeId(typeInfo) != INT32_TYPE) {
         mValid = false;
-        return 0;
+        mBuf = nullptr;
+        return bodyInfo;
     }
     mTagId = readNextValue<int32_t>();
     numElements--;
 
     parseAnnotations(getNumAnnotations(typeInfo));  // atom-level annotations
 
-    return numElements;
+    bodyInfo.numElements = numElements;
+    bodyInfo.buffer = mBuf;
+    bodyInfo.bufferSize = mRemainingLen;
+
+    mBuf = nullptr;
+    return bodyInfo;
 }
 
-// This parsing logic is tied to the encoding scheme used in StatsEvent.java and
-// stats_event.c
-bool LogEvent::parseBuffer(const uint8_t* buf, size_t len, bool fetchHeaderOnly) {
-    mBuf = buf;
-    mRemainingLen = (uint32_t)len;
+bool LogEvent::parseBody(const BodyBufferInfo& bodyInfo) {
+    mParsedHeaderOnly = false;
 
-    const uint8_t numElements = parseHeader();
-
-    if (!mValid || fetchHeaderOnly) {
-        mBuf = nullptr;
-        return mValid;
-    }
+    mBuf = bodyInfo.buffer;
+    mRemainingLen = (uint32_t)bodyInfo.bufferSize;
 
     int32_t pos[] = {1, 1, 1};
     bool last[] = {false, false, false};
 
-    for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) {
-        last[0] = (pos[0] == numElements);
+    for (pos[0] = 1; pos[0] <= bodyInfo.numElements && mValid; pos[0]++) {
+        last[0] = (pos[0] == bodyInfo.numElements);
 
         uint8_t typeInfo = readNextValue<uint8_t>();
         uint8_t typeId = getTypeId(typeInfo);
@@ -513,12 +592,22 @@
     return mValid;
 }
 
-uint8_t LogEvent::getTypeId(uint8_t typeInfo) {
-    return typeInfo & 0x0F;  // type id in lower 4 bytes
-}
+// This parsing logic is tied to the encoding scheme used in StatsEvent.java and
+// stats_event.c
+bool LogEvent::parseBuffer(const uint8_t* buf, size_t len) {
+    BodyBufferInfo bodyInfo = parseHeader(buf, len);
 
-uint8_t LogEvent::getNumAnnotations(uint8_t typeInfo) {
-    return (typeInfo >> 4) & 0x0F;  // num annotations in upper 4 bytes
+    // emphasize intention to parse the body, however atom data could be incomplete
+    // if header/body parsing was failed due to invalid buffer content for example
+    mParsedHeaderOnly = false;
+
+    // early termination if header is invalid
+    if (!mValid) {
+        mBuf = nullptr;
+        return false;
+    }
+
+    return parseBody(bodyInfo);
 }
 
 int64_t LogEvent::GetLong(size_t key, status_t* err) const {
@@ -661,6 +750,11 @@
         result += " [" + annotations + "] ";
     }
 
+    if (isParsedHeaderOnly()) {
+        result += " ParsedHeaderOnly }";
+        return result;
+    }
+
     for (const auto& value : mValues) {
         result += StringPrintf("%#x", value.mField.getField()) + "->" + value.mValue.toString();
         result += value.mAnnotations.toString() + " ";
diff --git a/statsd/src/logd/LogEvent.h b/statsd/src/logd/LogEvent.h
index c59cc73..d75b446 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 {
@@ -88,12 +89,31 @@
      * \param buf a buffer that begins at the start of the serialized atom (it
      * should not include the android_log_header_t or the StatsEventTag)
      * \param len size of the buffer
-     * \param fetchHeaderOnly force to parse only event header with atomId,timestamp
-     * and atom level annotations
      *
-     * \return success of the initialization
+     * \return success of the parsing
      */
-    bool parseBuffer(const uint8_t* buf, size_t len, bool fetchHeaderOnly = false);
+    bool parseBuffer(const uint8_t* buf, size_t len);
+
+    struct BodyBufferInfo {
+        const uint8_t* buffer = nullptr;
+        size_t bufferSize = 0;
+        uint8_t numElements = 0;
+    };
+
+    /**
+     * @brief Parses atom header which consists of atom id, timestamp
+     * and atom level annotations
+     * Updates the value of isValid()
+     * @return BodyBufferInfo to be used for parseBody()
+     */
+    BodyBufferInfo parseHeader(const uint8_t* buf, size_t len);
+
+    /**
+     * @brief Parses atom body which consists of header.numElements elements
+     * Should be called only with BodyBufferInfo if when logEvent.isValid() == true
+     * \return success of the parsing
+     */
+    bool parseBody(const BodyBufferInfo& bodyInfo);
 
     // Constructs a BinaryPushStateChanged LogEvent from API call.
     explicit LogEvent(const std::string& trainName, int64_t trainVersionCode, bool requiresStaging,
@@ -232,17 +252,26 @@
     }
 
     /**
+     * @brief Returns true if only header was parsed
+     */
+    bool isParsedHeaderOnly() const {
+        return mParsedHeaderOnly;
+    }
+
+    /**
      * Only use this if copy is absolutely needed.
      */
     LogEvent(const LogEvent&) = default;
 
+    inline StatsdRestrictionCategory getRestrictionCategory() const {
+        return mRestrictionCategory;
+    }
+
+    inline bool isRestricted() const {
+        return mRestrictionCategory != CATEGORY_NO_RESTRICTION;
+    }
+
 private:
-    /**
-     * @brief Parses atom header which consists of atom id, timestamp
-     * and atom level annotations
-     * @return amount of fields on the atom level
-     */
-    uint8_t parseHeader();
     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);
     void parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
@@ -265,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
@@ -277,6 +309,8 @@
 
     bool mValid = true; // stores whether the event we received from the socket is valid
 
+    bool mParsedHeaderOnly = false;  // stores whether the only header was parsed skipping the body
+
     /**
      * Side-effects:
      *    If there is enough space in buffer to read value of type T
@@ -317,9 +351,6 @@
         mValues.push_back(FieldValue(f, v));
     }
 
-    uint8_t getTypeId(uint8_t typeInfo);
-    uint8_t getNumAnnotations(uint8_t typeInfo);
-
     // The items are naturally sorted in DFS order as we read them. this allows us to do fast
     // matching.
     std::vector<FieldValue> mValues;
@@ -343,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.h b/statsd/src/logd/LogEventQueue.h
index 9dda3d2..e0e2f4e 100644
--- a/statsd/src/logd/LogEventQueue.h
+++ b/statsd/src/logd/LogEventQueue.h
@@ -16,12 +16,14 @@
 
 #pragma once
 
-#include "LogEvent.h"
+#include <gtest/gtest_prod.h>
 
 #include <condition_variable>
 #include <mutex>
 #include <queue>
 
+#include "LogEvent.h"
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -50,6 +52,16 @@
     std::condition_variable mCondition;
     std::mutex mMutex;
     std::queue<std::unique_ptr<LogEvent>> mQueue;
+
+    friend class SocketParseMessageTest;
+
+    FRIEND_TEST(SocketParseMessageTestNoFiltering, TestProcessMessageNoFiltering);
+    FRIEND_TEST(SocketParseMessageTestNoFiltering,
+                TestProcessMessageNoFilteringWithEmptySetExplicitSet);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterEmptySet);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterCompleteSet);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterPartialSet);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterToggle);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/main.cpp b/statsd/src/main.cpp
index 367ab73..14b31a0 100644
--- a/statsd/src/main.cpp
+++ b/statsd/src/main.cpp
@@ -17,6 +17,8 @@
 #define STATSD_DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
+#include <android/binder_ibinder.h>
+#include <android/binder_ibinder_platform.h>
 #include <android/binder_interface_utils.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
@@ -73,18 +75,36 @@
     ABinderProcess_startThreadPool();
 
     std::shared_ptr<LogEventQueue> eventQueue =
-            std::make_shared<LogEventQueue>(4000 /*buffer limit. Buffer is NOT pre-allocated*/);
+            std::make_shared<LogEventQueue>(8000 /*buffer limit. Buffer is NOT pre-allocated*/);
 
     // Initialize boot flags
-    FlagProvider::getInstance().initBootFlags({LIMIT_PULL_FLAG});
+    FlagProvider::getInstance().initBootFlags(
+            {OPTIMIZATION_SOCKET_PARSING_FLAG, STATSD_INIT_COMPLETED_NO_DELAY_FLAG});
 
     sp<UidMap> uidMap = UidMap::getInstance();
 
+    const bool logsFilteringEnabled = FlagProvider::getInstance().getBootFlagBool(
+            OPTIMIZATION_SOCKET_PARSING_FLAG, FLAG_FALSE);
+    std::shared_ptr<LogEventFilter> logEventFilter =
+            logsFilteringEnabled ? std::make_shared<LogEventFilter>() : nullptr;
+
+    const int initEventDelay = FlagProvider::getInstance().getBootFlagBool(
+                                       STATSD_INIT_COMPLETED_NO_DELAY_FLAG, FLAG_FALSE)
+                                       ? 0
+                                       : StatsService::kStatsdInitDelaySecs;
     // Create the service
-    gStatsService = SharedRefBase::make<StatsService>(uidMap, eventQueue);
+    gStatsService =
+            SharedRefBase::make<StatsService>(uidMap, eventQueue, logEventFilter, initEventDelay);
+    auto binder = gStatsService->asBinder();
+
+    // We want to be able to ask for the selinux context of callers:
+    if (__builtin_available(android __ANDROID_API_U__, *)) {
+        AIBinder_setRequestingSid(binder.get(), true);
+    }
+
     // TODO(b/149582373): Set DUMP_FLAG_PROTO once libbinder_ndk supports
     // setting dumpsys priorities.
-    binder_status_t status = AServiceManager_addService(gStatsService->asBinder().get(), "stats");
+    binder_status_t status = AServiceManager_addService(binder.get(), "stats");
     if (status != STATUS_OK) {
         ALOGE("Failed to add service as AIDL service");
         return -1;
@@ -94,7 +114,7 @@
 
     gStatsService->Startup();
 
-    gSocketListener = new StatsSocketListener(eventQueue);
+    gSocketListener = new StatsSocketListener(eventQueue, logEventFilter);
 
     ALOGI("Statsd starts to listen to socket.");
     // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
diff --git a/statsd/src/metrics/EventMetricProducer.h b/statsd/src/metrics/EventMetricProducer.h
index fc3e79d..c897305 100644
--- a/statsd/src/metrics/EventMetricProducer.h
+++ b/statsd/src/metrics/EventMetricProducer.h
@@ -50,6 +50,9 @@
         return METRIC_TYPE_EVENT;
     }
 
+protected:
+    size_t mTotalSize;
+
 private:
     void onMatchedLogEventInternalLocked(
             const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -95,8 +98,6 @@
 
     // 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;
 };
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/MetricProducer.h b/statsd/src/metrics/MetricProducer.h
index 0323523..ed3b217 100644
--- a/statsd/src/metrics/MetricProducer.h
+++ b/statsd/src/metrics/MetricProducer.h
@@ -31,8 +31,10 @@
 #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"
 
 namespace android {
@@ -335,6 +337,21 @@
     void writeActiveMetricToProtoOutputStream(
             int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
 
+    virtual void enforceRestrictedDataTtl(sqlite3* db, const 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;
diff --git a/statsd/src/metrics/MetricsManager.cpp b/statsd/src/metrics/MetricsManager.cpp
index 361cb1a..1822ddf 100644
--- a/statsd/src/metrics/MetricsManager.cpp
+++ b/statsd/src/metrics/MetricsManager.cpp
@@ -18,6 +18,7 @@
 
 #include "MetricsManager.h"
 
+#include <android-modules-utils/sdk_level.h>
 #include <private/android_filesystem_config.h>
 
 #include "CountMetricProducer.h"
@@ -33,6 +34,9 @@
 #include "stats_log_util.h"
 #include "stats_util.h"
 #include "statslog_statsd.h"
+#include "utils/DbUtils.h"
+
+using android::modules::sdklevel::IsAtLeastU;
 
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_INT32;
@@ -77,9 +81,16 @@
       mWhitelistedAtomIds(config.whitelisted_atom_ids().begin(),
                           config.whitelisted_atom_ids().end()),
       mShouldPersistHistory(config.persist_locally()) {
+    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,
@@ -119,6 +130,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;
@@ -421,6 +442,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) {
@@ -541,12 +567,13 @@
         return;
     }
 
+    // TODO(b/212755214): this check could be done once on the StatsLogProcessor level
     if (!eventSanityCheck(event)) {
         return;
     }
 
-    int tagId = event.GetTagId();
-    int64_t eventTimeNs = event.GetElapsedTimestampNs();
+    const int tagId = event.GetTagId();
+    const int64_t eventTimeNs = event.GetElapsedTimestampNs();
 
     bool isActive = mIsAlwaysActive;
 
@@ -573,6 +600,15 @@
         return;
     }
 
+    if (event.isParsedHeaderOnly()) {
+        // This should not happen if metric config is defined for certain atom id
+        const int64_t firstMatcherId =
+                mAllAtomMatchingTrackers[*matchersIt->second.begin()]->getId();
+        ALOGW("Atom %d is mistakenly skipped - there is a matcher %lld for it", tagId,
+              (long long)firstMatcherId);
+        return;
+    }
+
     vector<MatchingState> matcherCache(mAllAtomMatchingTrackers.size(),
                                        MatchingState::kNotComputed);
 
@@ -666,7 +702,6 @@
             }
         }
     }
-
     // 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) {
@@ -761,6 +796,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;
 }
 
@@ -769,7 +813,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;
@@ -778,6 +822,67 @@
                                                            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 {
+    for (const auto& [atomId, _] : mTagIdsToMatchersMap) {
+        allIds.insert(atomId);
+    }
 }
 
 }  // namespace statsd
diff --git a/statsd/src/metrics/MetricsManager.h b/statsd/src/metrics/MetricsManager.h
index 5b9731e..8b7869a 100644
--- a/statsd/src/metrics/MetricsManager.h
+++ b/statsd/src/metrics/MetricsManager.h
@@ -58,7 +58,7 @@
 
     bool eventSanityCheck(const LogEvent& event);
 
-    void onLogEvent(const LogEvent& event);
+    virtual void onLogEvent(const LogEvent& event);
 
     void onAnomalyAlarmFired(
         const int64_t& timestampNs,
@@ -161,6 +161,31 @@
     void loadMetadata(const metadata::StatsMetadata& metadata,
                       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(const 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;
+
 private:
     // For test only.
     inline int64_t getTtlEndNs() const { return mTtlEndNs; }
@@ -317,6 +342,10 @@
     // 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;
+
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
     FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
@@ -352,6 +381,9 @@
 
     FRIEND_TEST(MetricsManagerTest, TestLogSources);
     FRIEND_TEST(MetricsManagerTest, TestLogSourcesOnConfigUpdate);
+    FRIEND_TEST(MetricsManagerTest, TestOnMetricRemoveCalled);
+    FRIEND_TEST(MetricsManagerTest_SPlus, TestRestrictedMetricsConfig);
+    FRIEND_TEST(MetricsManagerTest_SPlus, TestRestrictedMetricsConfigUpdate);
     FRIEND_TEST(MetricsManagerUtilTest, TestSampledMetrics);
 
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
diff --git a/statsd/src/metrics/RestrictedEventMetricProducer.cpp b/statsd/src/metrics/RestrictedEventMetricProducer.cpp
new file mode 100644
index 0000000..f38e835
--- /dev/null
+++ b/statsd/src/metrics/RestrictedEventMetricProducer.cpp
@@ -0,0 +1,158 @@
+/*
+ * 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 unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+        const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+        const vector<int>& slicedStateAtoms,
+        const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+    : EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard, protoHash,
+                          startTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
+                          stateGroupMap),
+      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..74b0b80
--- /dev/null
+++ b/statsd/src/metrics/RestrictedEventMetricProducer.h
@@ -0,0 +1,67 @@
+#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, const int64_t startTimeNs,
+            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, const 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/parsing_utils/config_update_utils.cpp b/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index c3972d9..e7f30bc 100644
--- a/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -744,6 +744,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;
@@ -1058,6 +1064,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;
 }
 
diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index be41c90..7d2ed03 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"
 
@@ -788,6 +789,11 @@
         return nullopt;
     }
 
+    if (config.has_restricted_metrics_delegate_package_name()) {
+        return {new RestrictedEventMetricProducer(
+                key, metric, conditionIndex, initialConditionCache, wizard, metricHash, timeBaseNs,
+                eventActivationMap, eventDeactivationMap)};
+    }
     return {new EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
                                     metricHash, timeBaseNs, eventActivationMap,
                                     eventDeactivationMap)};
@@ -1440,6 +1446,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;
diff --git a/statsd/src/packages/UidMap.cpp b/statsd/src/packages/UidMap.cpp
index a6aef5b..a483247 100644
--- a/statsd/src/packages/UidMap.cpp
+++ b/statsd/src/packages/UidMap.cpp
@@ -71,7 +71,7 @@
 const int FIELD_ID_CHANGE_NEW_VERSION_STRING_HASH = 10;
 const int FIELD_ID_CHANGE_PREV_VERSION_STRING_HASH = 11;
 
-UidMap::UidMap() : mBytesUsed(0), mIncludeCertificateHash(false) {
+UidMap::UidMap() : mBytesUsed(0) {
 }
 
 UidMap::~UidMap() {}
@@ -351,7 +351,7 @@
                                       FIELD_ID_SNAPSHOT_PACKAGE_INFO);
         // Get installer index.
         int installerIndex = -1;
-        if (includeInstaller && mIncludeCertificateHash && installerIndices != nullptr) {
+        if (includeInstaller && installerIndices != nullptr) {
             const auto& it = installerIndices->find(appData.installer);
             if (it == installerIndices->end()) {
                 // We have not encountered this installer yet; add it to installerIndices.
@@ -400,17 +400,13 @@
             }
         }
 
-        if (mIncludeCertificateHash) {
-            const size_t dumpHashSize =
-                    truncatedCertificateHashSize <= appData.certificateHash.size()
-                            ? truncatedCertificateHashSize
-                            : appData.certificateHash.size();
-            if (dumpHashSize > 0) {
-                proto->write(
-                        FIELD_TYPE_BYTES | FIELD_ID_SNAPSHOT_PACKAGE_TRUNCATED_CERTIFICATE_HASH,
-                        reinterpret_cast<const char*>(appData.certificateHash.data()),
-                        dumpHashSize);
-            }
+        const size_t dumpHashSize = truncatedCertificateHashSize <= appData.certificateHash.size()
+                                            ? truncatedCertificateHashSize
+                                            : appData.certificateHash.size();
+        if (dumpHashSize > 0) {
+            proto->write(FIELD_TYPE_BYTES | FIELD_ID_SNAPSHOT_PACKAGE_TRUNCATED_CERTIFICATE_HASH,
+                         reinterpret_cast<const char*>(appData.certificateHash.data()),
+                         dumpHashSize);
         }
 
         proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION,
@@ -481,7 +477,7 @@
         installers[index] = installer;
     }
 
-    if (includeInstaller && mIncludeCertificateHash) {
+    if (includeInstaller) {
         // Write installer list; either strings or hashes.
         for (const string& installerName : installers) {
             if (str_set == nullptr) {  // Strings not hashed
@@ -554,11 +550,6 @@
     return results;
 }
 
-void UidMap::setIncludeCertificateHash(const bool include) {
-    lock_guard<mutex> lock(mMutex);
-    mIncludeCertificateHash = include;
-}
-
 // Note not all the following AIDs are used as uids. Some are used only for gids.
 // It's ok to leave them in the map, but we won't ever see them in the log's uid field.
 // App's uid starts from 10000, and will not overlap with the following AIDs.
diff --git a/statsd/src/packages/UidMap.h b/statsd/src/packages/UidMap.h
index e5cd224..43f9967 100644
--- a/statsd/src/packages/UidMap.h
+++ b/statsd/src/packages/UidMap.h
@@ -169,8 +169,6 @@
                              std::map<string, int>* installerIndices, std::set<string>* str_set,
                              ProtoOutputStream* proto) const;
 
-    void setIncludeCertificateHash(const bool include);
-
 private:
     std::set<string> getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const;
     string normalizeAppName(const string& appName) const;
@@ -227,9 +225,10 @@
     // Cache the size of mOutput;
     size_t mBytesUsed;
 
-    bool mIncludeCertificateHash;
-
     // 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 8c9f57a..3b0c606 100644
--- a/statsd/src/shell/ShellSubscriber.cpp
+++ b/statsd/src/shell/ShellSubscriber.cpp
@@ -20,9 +20,12 @@
 
 #include <android-base/file.h>
 #include <inttypes.h>
+#include <utils/Timers.h>
 
 #include "stats_log_util.h"
 
+using aidl::android::os::IStatsSubscriptionCallback;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -31,6 +34,7 @@
     {
         std::unique_lock<std::mutex> lock(mMutex);
         mClientSet.clear();
+        updateLogEventFilterLocked();
     }
     mThreadSleepCV.notify_one();
     if (mThread.joinable()) {
@@ -38,10 +42,9 @@
     }
 }
 
-// Create new ShellSubscriberClient to manage a new subscription
 bool ShellSubscriber::startNewSubscription(int in, int out, int64_t timeoutSec) {
     std::unique_lock<std::mutex> lock(mMutex);
-    ALOGD("ShellSubscriber: new subscription has come in");
+    VLOG("ShellSubscriber: new subscription has come in");
     if (mClientSet.size() >= kMaxSubscriptions) {
         ALOGE("ShellSubscriber: cannot have another active subscription. Current Subscriptions: "
               "%zu. Limit: %zu",
@@ -49,12 +52,31 @@
         return false;
     }
 
-    unique_ptr<ShellSubscriberClient> client = make_unique<ShellSubscriberClient>(
-            in, out, timeoutSec, getElapsedRealtimeSec(), mUidMap, mPullerMgr);
-    if (!client->readConfig()) return false;
+    return startNewSubscriptionLocked(ShellSubscriberClient::create(
+            in, out, timeoutSec, getElapsedRealtimeSec(), mUidMap, mPullerMgr));
+}
+
+bool ShellSubscriber::startNewSubscription(const vector<uint8_t>& subscriptionConfig,
+                                           const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    std::unique_lock<std::mutex> lock(mMutex);
+    VLOG("ShellSubscriber: new subscription has come in");
+    if (mClientSet.size() >= kMaxSubscriptions) {
+        ALOGE("ShellSubscriber: cannot have another active subscription. Current Subscriptions: "
+              "%zu. Limit: %zu",
+              mClientSet.size(), kMaxSubscriptions);
+        return false;
+    }
+
+    return startNewSubscriptionLocked(ShellSubscriberClient::create(
+            subscriptionConfig, callback, getElapsedRealtimeSec(), mUidMap, mPullerMgr));
+}
+
+bool ShellSubscriber::startNewSubscriptionLocked(unique_ptr<ShellSubscriberClient> client) {
+    if (client == nullptr) return false;
 
     // Add new valid client to the client set
     mClientSet.insert(std::move(client));
+    updateLogEventFilterLocked();
 
     // Only spawn one thread to manage pulling atoms and sending
     // heartbeats.
@@ -65,18 +87,19 @@
         }
         mThread = thread([this] { pullAndSendHeartbeats(); });
     }
+
     return true;
 }
 
 // Sends heartbeat signals and sleeps between doing work
 void ShellSubscriber::pullAndSendHeartbeats() {
-    ALOGD("ShellSubscriber: helper thread starting");
+    VLOG("ShellSubscriber: helper thread starting");
     std::unique_lock<std::mutex> lock(mMutex);
     while (true) {
-        int64_t sleepTimeMs = INT_MAX;
-        int64_t nowSecs = getElapsedRealtimeSec();
-        int64_t nowMillis = getElapsedRealtimeMillis();
-        int64_t nowNanos = getElapsedRealtimeNs();
+        int64_t sleepTimeMs = 24 * 60 * 60 * 1000;  // 24 hours.
+        const int64_t nowNanos = getElapsedRealtimeNs();
+        const int64_t nowMillis = nanoseconds_to_milliseconds(nowNanos);
+        const int64_t nowSecs = nanoseconds_to_seconds(nowNanos);
         for (auto clientIt = mClientSet.begin(); clientIt != mClientSet.end();) {
             int64_t subscriptionSleepMs =
                     (*clientIt)->pullAndSendHeartbeatsIfNeeded(nowSecs, nowMillis, nowNanos);
@@ -84,13 +107,14 @@
             if ((*clientIt)->isAlive()) {
                 ++clientIt;
             } else {
-                ALOGD("ShellSubscriber: removing client!");
+                VLOG("ShellSubscriber: removing client!");
                 clientIt = mClientSet.erase(clientIt);
+                updateLogEventFilterLocked();
             }
         }
         if (mClientSet.empty()) {
             mThreadAlive = false;
-            ALOGD("ShellSubscriber: helper thread done!");
+            VLOG("ShellSubscriber: helper thread done!");
             return;
         }
         VLOG("ShellSubscriber: helper thread sleeping for %" PRId64 "ms", sleepTimeMs);
@@ -99,18 +123,85 @@
 }
 
 void ShellSubscriber::onLogEvent(const LogEvent& event) {
+    // 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);
         if ((*clientIt)->isAlive()) {
             ++clientIt;
         } else {
-            ALOGD("ShellSubscriber: removing client!");
+            VLOG("ShellSubscriber: removing client!");
             clientIt = mClientSet.erase(clientIt);
+            updateLogEventFilterLocked();
         }
     }
 }
 
+void ShellSubscriber::flushSubscription(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    std::unique_lock<std::mutex> lock(mMutex);
+
+    // TODO(b/268822860): Consider storing callback clients in a map keyed by
+    // IStatsSubscriptionCallback to avoid this linear search.
+    for (auto clientIt = mClientSet.begin(); clientIt != mClientSet.end(); ++clientIt) {
+        if ((*clientIt)->hasCallback(callback)) {
+            if ((*clientIt)->isAlive()) {
+                (*clientIt)->flush();
+            } else {
+                VLOG("ShellSubscriber: removing client!");
+
+                // Erasing a value moves the iterator to the next value. The update expression also
+                // moves the iterator, skipping a value. This is fine because we do an early return
+                // before next iteration of the loop.
+                clientIt = mClientSet.erase(clientIt);
+                updateLogEventFilterLocked();
+            }
+            return;
+        }
+    }
+}
+
+void ShellSubscriber::unsubscribe(const shared_ptr<IStatsSubscriptionCallback>& callback) {
+    std::unique_lock<std::mutex> lock(mMutex);
+
+    // TODO(b/268822860): Consider storing callback clients in a map keyed by
+    // IStatsSubscriptionCallback to avoid this linear search.
+    for (auto clientIt = mClientSet.begin(); clientIt != mClientSet.end(); ++clientIt) {
+        if ((*clientIt)->hasCallback(callback)) {
+            if ((*clientIt)->isAlive()) {
+                (*clientIt)->onUnsubscribe();
+            }
+            VLOG("ShellSubscriber: removing client!");
+
+            // Erasing a value moves the iterator to the next value. The update expression also
+            // moves the iterator, skipping a value. This is fine because we do an early return
+            // before next iteration of the loop.
+            clientIt = mClientSet.erase(clientIt);
+            updateLogEventFilterLocked();
+            return;
+        }
+    }
+}
+
+void ShellSubscriber::updateLogEventFilterLocked() const {
+    VLOG("ShellSubscriber: Updating allAtomIds");
+    if (!mLogEventFilter) {
+        return;
+    }
+    LogEventFilter::AtomIdSet allAtomIds;
+    for (const auto& client : mClientSet) {
+        client->addAllAtomIds(allAtomIds);
+    }
+    VLOG("ShellSubscriber: Updating allAtomIds done. Total atoms %d", (int)allAtomIds.size());
+    mLogEventFilter->setAtomIds(std::move(allAtomIds), this);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/shell/ShellSubscriber.h b/statsd/src/shell/ShellSubscriber.h
index 3b25281..4ff8861 100644
--- a/statsd/src/shell/ShellSubscriber.h
+++ b/statsd/src/shell/ShellSubscriber.h
@@ -16,13 +16,15 @@
 
 #pragma once
 
+#include <aidl/android/os/IStatsSubscriptionCallback.h>
+
 #include <condition_variable>
 #include <mutex>
 #include <thread>
 
 #include "external/StatsPullerManager.h"
 #include "packages/UidMap.h"
-#include "src/shell/ShellSubscriberClient.h"
+#include "shell/ShellSubscriberClient.h"
 #include "src/shell/shell_config.pb.h"
 #include "src/statsd_config.pb.h"
 
@@ -54,15 +56,27 @@
  */
 class ShellSubscriber : public virtual RefBase {
 public:
-    ShellSubscriber(sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr)
-        : mUidMap(uidMap), mPullerMgr(pullerMgr){};
+    ShellSubscriber(sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr,
+                    const std::shared_ptr<LogEventFilter>& logEventFilter)
+        : mUidMap(uidMap), mPullerMgr(pullerMgr), mLogEventFilter(logEventFilter){};
 
     ~ShellSubscriber();
 
+    // Create new ShellSubscriberClient with file descriptors to manage a new subscription.
     bool startNewSubscription(int inFd, int outFd, int64_t timeoutSec);
 
+    // Create new ShellSubscriberClient with Binder callback to manage a new subscription.
+    bool startNewSubscription(
+            const vector<uint8_t>& subscriptionConfig,
+            const shared_ptr<aidl::android::os::IStatsSubscriptionCallback>& callback);
+
     void onLogEvent(const LogEvent& event);
 
+    void flushSubscription(
+            const shared_ptr<aidl::android::os::IStatsSubscriptionCallback>& callback);
+
+    void unsubscribe(const shared_ptr<aidl::android::os::IStatsSubscriptionCallback>& callback);
+
     static size_t getMaxSizeKb() {
         return ShellSubscriberClient::getMaxSizeKb();
     }
@@ -72,12 +86,19 @@
     }
 
 private:
+    bool startNewSubscriptionLocked(unique_ptr<ShellSubscriberClient> client);
+
     void pullAndSendHeartbeats();
 
+    /* Tells LogEventFilter about atom ids to parse */
+    void updateLogEventFilterLocked() const;
+
     sp<UidMap> mUidMap;
 
     sp<StatsPullerManager> mPullerMgr;
 
+    std::shared_ptr<LogEventFilter> mLogEventFilter;
+
     // Protects mClientSet, mThreadAlive, and ShellSubscriberClient
     mutable std::mutex mMutex;
 
diff --git a/statsd/src/shell/ShellSubscriberClient.cpp b/statsd/src/shell/ShellSubscriberClient.cpp
index eb93772..546ed16 100644
--- a/statsd/src/shell/ShellSubscriberClient.cpp
+++ b/statsd/src/shell/ShellSubscriberClient.cpp
@@ -18,65 +18,41 @@
 
 #include "ShellSubscriberClient.h"
 
+#include "FieldValue.h"
 #include "matchers/matcher_util.h"
 #include "stats_log_util.h"
 
 using android::base::unique_fd;
 using android::util::ProtoOutputStream;
+using Status = ::ndk::ScopedAStatus;
 
 namespace android {
 namespace os {
 namespace statsd {
 
-const static int FIELD_ID_ATOM = 1;
+const static int FIELD_ID_SHELL_DATA__ATOM = 1;
+const static int FIELD_ID_SHELL_DATA__ELAPSED_TIMESTAMP_NANOS = 2;
 
-// Called by ShellSubscriber when a pushed event occurs
-void ShellSubscriberClient::onLogEvent(const LogEvent& event) {
-    mProto.clear();
-    for (const auto& matcher : mPushedMatchers) {
-        if (matchesSimple(mUidMap, matcher, event)) {
-            uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
-                                              util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
-            event.ToProto(mProto);
-            mProto.end(atomToken);
-            attemptWriteToPipeLocked(mProto.size());
-        }
-    }
-}
+struct ReadConfigResult {
+    vector<SimpleAtomMatcher> pushedMatchers;
+    vector<ShellSubscriberClient::PullInfo> pullInfo;
+};
 
-// Read and parse single config. There should only one config per input.
-bool ShellSubscriberClient::readConfig() {
-    // Read the size of the config.
-    size_t bufferSize;
-    if (!android::base::ReadFully(mDupIn.get(), &bufferSize, sizeof(bufferSize))) {
-        return false;
-    }
-
-    // Check bufferSize
-    if (bufferSize > (kMaxSizeKb * 1024)) {
-        ALOGE("ShellSubscriberClient: received config (%zu bytes) is larger than the max size (%zu "
-              "bytes)",
-              bufferSize, (kMaxSizeKb * 1024));
-        return false;
-    }
-
-    // Read the config.
-    vector<uint8_t> buffer(bufferSize);
-    if (!android::base::ReadFully(mDupIn.get(), buffer.data(), bufferSize)) {
-        return false;
-    }
-
+// Read and parse single config. There should only one config in the input.
+static optional<ReadConfigResult> readConfig(const vector<uint8_t>& configBytes,
+                                             int64_t startTimeMs, int64_t minPullIntervalMs) {
     // Parse the config.
     ShellSubscription config;
-    if (!config.ParseFromArray(buffer.data(), bufferSize)) {
-        return false;
+    if (!config.ParseFromArray(configBytes.data(), configBytes.size())) {
+        ALOGE("ShellSubscriberClient: failed to parse the config");
+        return nullopt;
     }
 
-    // Update SubscriptionInfo with state from config
-    for (const auto& pushed : config.pushed()) {
-        mPushedMatchers.push_back(pushed);
-    }
+    ReadConfigResult result;
 
+    result.pushedMatchers.assign(config.pushed().begin(), config.pushed().end());
+
+    vector<ShellSubscriberClient::PullInfo> pullInfo;
     for (const auto& pulled : config.pulled()) {
         vector<string> packages;
         vector<int32_t> uids;
@@ -89,89 +65,245 @@
             }
         }
 
-        mPulledInfo.emplace_back(pulled.matcher(), pulled.freq_millis(), packages, uids);
+        const int64_t pullIntervalMs = max(pulled.freq_millis(), minPullIntervalMs);
+        result.pullInfo.emplace_back(pulled.matcher(), startTimeMs, pullIntervalMs, packages, uids);
         ALOGD("ShellSubscriberClient: adding matcher for pulled atom %d",
               pulled.matcher().atom_id());
     }
 
+    return result;
+}
+
+ShellSubscriberClient::PullInfo::PullInfo(const SimpleAtomMatcher& matcher, int64_t startTimeMs,
+                                          int64_t intervalMs,
+                                          const std::vector<std::string>& packages,
+                                          const std::vector<int32_t>& uids)
+    : mPullerMatcher(matcher),
+      mIntervalMs(intervalMs),
+      mPrevPullElapsedRealtimeMs(startTimeMs),
+      mPullPackages(packages),
+      mPullUids(uids) {
+}
+
+ShellSubscriberClient::ShellSubscriberClient(
+        int out, const std::shared_ptr<IStatsSubscriptionCallback>& callback,
+        const std::vector<SimpleAtomMatcher>& pushedMatchers,
+        const std::vector<PullInfo>& pulledInfo, int64_t timeoutSec, int64_t startTimeSec,
+        const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerMgr)
+    : mUidMap(uidMap),
+      mPullerMgr(pullerMgr),
+      mDupOut(fcntl(out, F_DUPFD_CLOEXEC, 0)),
+      mPushedMatchers(pushedMatchers),
+      mPulledInfo(pulledInfo),
+      mCallback(callback),
+      mTimeoutSec(timeoutSec),
+      mStartTimeSec(startTimeSec),
+      mLastWriteMs(startTimeSec * 1000),
+      mCacheSize(0){};
+
+unique_ptr<ShellSubscriberClient> ShellSubscriberClient::create(
+        int in, int out, int64_t timeoutSec, int64_t startTimeSec, const sp<UidMap>& uidMap,
+        const sp<StatsPullerManager>& pullerMgr) {
+    // Read the size of the config.
+    size_t bufferSize;
+    if (!android::base::ReadFully(in, &bufferSize, sizeof(bufferSize))) {
+        return nullptr;
+    }
+
+    // Check bufferSize
+    if (bufferSize > (kMaxSizeKb * 1024)) {
+        ALOGE("ShellSubscriberClient: received config (%zu bytes) is larger than the max size (%zu "
+              "bytes)",
+              bufferSize, (kMaxSizeKb * 1024));
+        return nullptr;
+    }
+
+    // Read the config.
+    vector<uint8_t> buffer(bufferSize);
+    if (!android::base::ReadFully(in, buffer.data(), bufferSize)) {
+        ALOGE("ShellSubscriberClient: failed to read the config from file descriptor");
+        return nullptr;
+    }
+
+    const optional<ReadConfigResult> readConfigResult =
+            readConfig(buffer, startTimeSec * 1000, /* minPullIntervalMs */ 0);
+    if (!readConfigResult.has_value()) {
+        return nullptr;
+    }
+
+    return make_unique<ShellSubscriberClient>(
+            out, /*callback=*/nullptr, readConfigResult->pushedMatchers, readConfigResult->pullInfo,
+            timeoutSec, startTimeSec, uidMap, pullerMgr);
+}
+
+unique_ptr<ShellSubscriberClient> ShellSubscriberClient::create(
+        const vector<uint8_t>& subscriptionConfig,
+        const shared_ptr<IStatsSubscriptionCallback>& callback, int64_t startTimeSec,
+        const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerMgr) {
+    if (callback == nullptr) {
+        ALOGE("ShellSubscriberClient: received nullptr callback");
+        return nullptr;
+    }
+
+    if (subscriptionConfig.size() > (kMaxSizeKb * 1024)) {
+        ALOGE("ShellSubscriberClient: received config (%zu bytes) is larger than the max size (%zu "
+              "bytes)",
+              subscriptionConfig.size(), (kMaxSizeKb * 1024));
+        return nullptr;
+    }
+
+    const optional<ReadConfigResult> readConfigResult =
+            readConfig(subscriptionConfig, startTimeSec * 1000,
+                       ShellSubscriberClient::kMinCallbackPullIntervalMs);
+    if (!readConfigResult.has_value()) {
+        return nullptr;
+    }
+
+    return make_unique<ShellSubscriberClient>(
+            /*out=*/-1, callback, readConfigResult->pushedMatchers, readConfigResult->pullInfo,
+            /*timeoutSec=*/-1, startTimeSec, uidMap, pullerMgr);
+}
+
+bool ShellSubscriberClient::writeEventToProtoIfMatched(const LogEvent& event,
+                                                       const SimpleAtomMatcher& matcher,
+                                                       const sp<UidMap>& uidMap) {
+    if (!matchesSimple(uidMap, matcher, event)) {
+        return false;
+    }
+
+    // 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);
+    mProtoOut.end(atomToken);
+
+    const int64_t timestampNs = truncateTimestampIfNecessary(event);
+    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);
+
     return true;
 }
 
+// Called by ShellSubscriber when a pushed event occurs
+void ShellSubscriberClient::onLogEvent(const LogEvent& event) {
+    for (const auto& matcher : mPushedMatchers) {
+        if (writeEventToProtoIfMatched(event, matcher, mUidMap)) {
+            flushProtoIfNeeded();
+            break;
+        }
+    }
+}
+
+void ShellSubscriberClient::flushProtoIfNeeded() {
+    if (mCallback == nullptr) {  // Using file descriptor.
+        triggerFdFlush();
+    } else if (mCacheSize >= kMaxCacheSizeBytes) {  // Using callback.
+        // Flush data if cache is full.
+        triggerCallback(StatsSubscriptionCallbackReason::STATSD_INITIATED);
+    }
+}
+
+int64_t ShellSubscriberClient::pullIfNeeded(int64_t nowSecs, int64_t nowMillis, int64_t nowNanos) {
+    int64_t sleepTimeMs = 24 * 60 * 60 * 1000;  // 24 hours.
+    for (PullInfo& pullInfo : mPulledInfo) {
+        if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mIntervalMs <= nowMillis) {
+            vector<int32_t> uids;
+            getUidsForPullAtom(&uids, pullInfo);
+
+            vector<shared_ptr<LogEvent>> data;
+            mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), uids, nowNanos, &data);
+            VLOG("ShellSubscriberClient: pulled %zu atoms with id %d", data.size(),
+                 pullInfo.mPullerMatcher.atom_id());
+
+            writePulledAtomsLocked(data, pullInfo.mPullerMatcher);
+            pullInfo.mPrevPullElapsedRealtimeMs = nowMillis;
+        }
+
+        // Determine how long to sleep before doing more work.
+        const int64_t nextPullTimeMs = pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mIntervalMs;
+
+        const int64_t timeBeforePullMs =
+                nextPullTimeMs - nowMillis;  // guaranteed to be non-negative
+        sleepTimeMs = min(sleepTimeMs, timeBeforePullMs);
+    }
+    return sleepTimeMs;
+}
+
 // The pullAndHeartbeat threads sleep for the minimum time
 // among all clients' input
 int64_t ShellSubscriberClient::pullAndSendHeartbeatsIfNeeded(int64_t nowSecs, int64_t nowMillis,
                                                              int64_t nowNanos) {
-    int64_t sleepTimeMs = kMsBetweenHeartbeats;
-
-    if ((nowSecs - mStartTimeSec >= mTimeoutSec) && (mTimeoutSec > 0)) {
-        mClientAlive = false;
-        return sleepTimeMs;
-    }
-
-    for (PullInfo& pullInfo : mPulledInfo) {
-        if (pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval >= nowMillis) {
-            continue;
+    int64_t sleepTimeMs;
+    if (mCallback == nullptr) {  // File descriptor subscription
+        if ((nowSecs - mStartTimeSec >= mTimeoutSec) && (mTimeoutSec > 0)) {
+            mClientAlive = false;
+            return kMsBetweenHeartbeats;
         }
 
-        vector<int32_t> uids;
-        getUidsForPullAtom(&uids, pullInfo);
+        sleepTimeMs = min(kMsBetweenHeartbeats, pullIfNeeded(nowSecs, nowMillis, nowNanos));
 
-        vector<std::shared_ptr<LogEvent>> data;
-        mPullerMgr->Pull(pullInfo.mPullerMatcher.atom_id(), uids, nowNanos, &data);
-        VLOG("ShellSubscriberClient: pulled %zu atoms with id %d", data.size(),
-             pullInfo.mPullerMatcher.atom_id());
-        writePulledAtomsLocked(data, pullInfo.mPullerMatcher);
+        // Send a heartbeat consisting of data size of 0, if
+        // the user hasn't recently received data from statsd. When it receives the data size of 0,
+        // the user will not expect any atoms and recheck whether the subscription should end.
+        if (nowMillis - mLastWriteMs >= kMsBetweenHeartbeats) {
+            triggerFdFlush();
+            if (!mClientAlive) return kMsBetweenHeartbeats;
+        }
 
-        pullInfo.mPrevPullElapsedRealtimeMs = nowMillis;
+        int64_t timeBeforeHeartbeat = mLastWriteMs + kMsBetweenHeartbeats - nowMillis;
+        sleepTimeMs = min(sleepTimeMs, timeBeforeHeartbeat);
+    } else {  // Callback subscription.
+        sleepTimeMs = min(kMsBetweenCallbacks, pullIfNeeded(nowSecs, nowMillis, nowNanos));
+
+        if (mCacheSize > 0 && nowMillis - mLastWriteMs >= kMsBetweenCallbacks) {
+            // Flush data if cache has kept data for longer than kMsBetweenCallbacks.
+            triggerCallback(StatsSubscriptionCallbackReason::STATSD_INITIATED);
+        }
+
+        // Cache should be flushed kMsBetweenCallbacks after mLastWrite.
+        const int64_t timeToCallbackMs = mLastWriteMs + kMsBetweenCallbacks - nowMillis;
+
+        // For callback subscriptions, ensure minimum sleep time is at least
+        // kMinCallbackSleepIntervalMs. Even if there is less than kMinCallbackSleepIntervalMs left
+        // before next pull time, sleep for at least kMinCallbackSleepIntervalMs. This has the
+        // effect of multiple pulled atoms that have a pull within kMinCallbackSleepIntervalMs from
+        // now to have their pulls batched together, mitigating frequent wakeups of the puller
+        // thread.
+        sleepTimeMs = max(kMinCallbackSleepIntervalMs, min(sleepTimeMs, timeToCallbackMs));
     }
-
-    // Send a heartbeat, consisting of a data size of 0, if the user hasn't recently received
-    // data from statsd. When it receives the data size of 0, the user will not expect any
-    // atoms and recheck whether the subscription should end.
-    if (nowMillis - mLastWriteMs >= kMsBetweenHeartbeats) {
-        attemptWriteToPipeLocked(/*dataSize=*/0);
-        if (!mClientAlive) return kMsBetweenHeartbeats;
-    }
-
-    // Determine how long to sleep before doing more work.
-    for (PullInfo& pullInfo : mPulledInfo) {
-        int64_t nextPullTime = pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval;
-        int64_t timeBeforePull = nextPullTime - nowMillis;  // guaranteed to be non-negative
-        sleepTimeMs = std::min(sleepTimeMs, timeBeforePull);
-    }
-    int64_t timeBeforeHeartbeat = (mLastWriteMs + kMsBetweenHeartbeats) - nowMillis;
-    sleepTimeMs = std::min(sleepTimeMs, timeBeforeHeartbeat);
     return sleepTimeMs;
 }
 
-void ShellSubscriberClient::writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
+void ShellSubscriberClient::writePulledAtomsLocked(const vector<shared_ptr<LogEvent>>& data,
                                                    const SimpleAtomMatcher& matcher) {
-    mProto.clear();
-    int count = 0;
-    for (const auto& event : data) {
-        if (matchesSimple(mUidMap, matcher, *event)) {
-            count++;
-            uint64_t atomToken = mProto.start(util::FIELD_TYPE_MESSAGE |
-                                              util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
-            event->ToProto(mProto);
-            mProto.end(atomToken);
+    bool hasData = false;
+    for (const shared_ptr<LogEvent>& event : data) {
+        if (writeEventToProtoIfMatched(*event, matcher, mUidMap)) {
+            hasData = true;
         }
     }
 
-    if (count > 0) attemptWriteToPipeLocked(mProto.size());
+    if (hasData) {
+        flushProtoIfNeeded();
+    }
 }
 
-// Tries to write the atom encoded in mProto to the pipe. If the write fails
+// Tries to write the atom encoded in mProtoOut to the pipe. If the write fails
 // because the read end of the pipe has closed, change the client status so
 // the manager knows the subscription is no longer active
-void ShellSubscriberClient::attemptWriteToPipeLocked(size_t dataSize) {
+void ShellSubscriberClient::attemptWriteToPipeLocked() {
+    const size_t dataSize = mProtoOut.size();
     // First, write the payload size.
     if (!android::base::WriteFully(mDupOut, &dataSize, sizeof(dataSize))) {
         mClientAlive = false;
         return;
     }
     // Then, write the payload if this is not just a heartbeat.
-    if (dataSize > 0 && !mProto.flush(mDupOut.get())) {
+    if (dataSize > 0 && !mProtoOut.flush(mDupOut.get())) {
         mClientAlive = false;
         return;
     }
@@ -188,6 +320,45 @@
     uids->push_back(DEFAULT_PULL_UID);
 }
 
+void ShellSubscriberClient::clearCache() {
+    mProtoOut.clear();
+    mCacheSize = 0;
+}
+
+void ShellSubscriberClient::triggerFdFlush() {
+    attemptWriteToPipeLocked();
+    clearCache();
+}
+
+void ShellSubscriberClient::triggerCallback(StatsSubscriptionCallbackReason reason) {
+    // Invoke Binder callback with cached event data.
+    vector<uint8_t> payloadBytes;
+    mProtoOut.serializeToVector(&payloadBytes);
+    const Status status = mCallback->onSubscriptionData(reason, payloadBytes);
+    if (status.getStatus() == STATUS_DEAD_OBJECT &&
+        status.getExceptionCode() == EX_TRANSACTION_FAILED) {
+        mClientAlive = false;
+        return;
+    }
+
+    mLastWriteMs = getElapsedRealtimeMillis();
+    clearCache();
+}
+
+void ShellSubscriberClient::flush() {
+    triggerCallback(StatsSubscriptionCallbackReason::FLUSH_REQUESTED);
+}
+
+void ShellSubscriberClient::onUnsubscribe() {
+    triggerCallback(StatsSubscriptionCallbackReason::SUBSCRIPTION_ENDED);
+}
+
+void ShellSubscriberClient::addAllAtomIds(LogEventFilter::AtomIdSet& allAtomIds) const {
+    for (const auto& matcher : mPushedMatchers) {
+        allAtomIds.insert(matcher.atom_id());
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/shell/ShellSubscriberClient.h b/statsd/src/shell/ShellSubscriberClient.h
index 31ec1d4..9d03cb4 100644
--- a/statsd/src/shell/ShellSubscriberClient.h
+++ b/statsd/src/shell/ShellSubscriberClient.h
@@ -16,16 +16,24 @@
 
 #pragma once
 
+#include <aidl/android/os/IStatsSubscriptionCallback.h>
+#include <aidl/android/os/StatsSubscriptionCallbackReason.h>
 #include <android-base/file.h>
 #include <android/util/ProtoOutputStream.h>
 #include <private/android_filesystem_config.h>
 
+#include <memory>
+
 #include "external/StatsPullerManager.h"
 #include "logd/LogEvent.h"
 #include "packages/UidMap.h"
+#include "socket/LogEventFilter.h"
 #include "src/shell/shell_config.pb.h"
 #include "src/statsd_config.pb.h"
 
+using aidl::android::os::IStatsSubscriptionCallback;
+using aidl::android::os::StatsSubscriptionCallbackReason;
+
 namespace android {
 namespace os {
 namespace statsd {
@@ -34,81 +42,123 @@
 // guarded by the mutex in ShellSubscriber.h
 class ShellSubscriberClient {
 public:
-    ShellSubscriberClient(int in, int out, int64_t timeoutSec, int64_t startTimeSec,
-                          sp<UidMap> uidMap, sp<StatsPullerManager> pullerMgr)
-        : mUidMap(uidMap),
-          mPullerMgr(pullerMgr),
-          mDupIn(dup(in)),
-          mDupOut(dup(out)),
-          mTimeoutSec(timeoutSec),
-          mStartTimeSec(startTimeSec){};
+    struct PullInfo {
+        PullInfo(const SimpleAtomMatcher& matcher, int64_t startTimeMs, int64_t interval,
+                 const std::vector<std::string>& packages, const std::vector<int32_t>& uids);
+
+        const SimpleAtomMatcher mPullerMatcher;
+        const int64_t mIntervalMs;
+        int64_t mPrevPullElapsedRealtimeMs;
+        const std::vector<std::string> mPullPackages;
+        const std::vector<int32_t> mPullUids;
+    };
+
+    static std::unique_ptr<ShellSubscriberClient> create(int in, int out, int64_t timeoutSec,
+                                                         int64_t startTimeSec,
+                                                         const sp<UidMap>& uidMap,
+                                                         const sp<StatsPullerManager>& pullerMgr);
+
+    static std::unique_ptr<ShellSubscriberClient> create(
+            const std::vector<uint8_t>& subscriptionConfig,
+            const std::shared_ptr<IStatsSubscriptionCallback>& callback, int64_t startTimeSec,
+            const sp<UidMap>& uidMap, const sp<StatsPullerManager>& pullerMgr);
+
+    // Should only be called by the create() factory.
+    explicit ShellSubscriberClient(int out,
+                                   const std::shared_ptr<IStatsSubscriptionCallback>& callback,
+                                   const std::vector<SimpleAtomMatcher>& pushedMatchers,
+                                   const std::vector<PullInfo>& pulledInfo, int64_t timeoutSec,
+                                   int64_t startTimeSec, const sp<UidMap>& uidMap,
+                                   const sp<StatsPullerManager>& pullerMgr);
 
     void onLogEvent(const LogEvent& event);
 
-    bool readConfig();
-
     int64_t pullAndSendHeartbeatsIfNeeded(int64_t nowSecs, int64_t nowMillis, int64_t nowNanos);
 
+    // Should only be called when mCallback is not nullptr.
+    void flush();
+
+    // Should only be called when mCallback is not nullptr.
+    void onUnsubscribe();
+
     bool isAlive() const {
         return mClientAlive;
     }
 
+    bool hasCallback(const std::shared_ptr<IStatsSubscriptionCallback>& callback) const {
+        return mCallback != nullptr && callback != nullptr &&
+               callback->asBinder() == mCallback->asBinder();
+    }
+
     static size_t getMaxSizeKb() {
         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.
 private:
-    struct PullInfo {
-        PullInfo(const SimpleAtomMatcher& matcher, int64_t interval,
-                 const std::vector<std::string>& packages, const std::vector<int32_t>& uids)
-            : mPullerMatcher(matcher),
-              mInterval(interval),
-              mPrevPullElapsedRealtimeMs(0),
-              mPullPackages(packages),
-              mPullUids(uids) {
-        }
-        SimpleAtomMatcher mPullerMatcher;
-        int64_t mInterval;
-        int64_t mPrevPullElapsedRealtimeMs;
-        std::vector<std::string> mPullPackages;
-        std::vector<int32_t> mPullUids;
-    };
+    int64_t pullIfNeeded(int64_t nowSecs, int64_t nowMillis, int64_t nowNanos);
 
     void writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
                                 const SimpleAtomMatcher& matcher);
 
-    void attemptWriteToPipeLocked(size_t dataSize);
+    void attemptWriteToPipeLocked();
 
     void getUidsForPullAtom(vector<int32_t>* uids, const PullInfo& pullInfo);
 
+    void flushProtoIfNeeded();
+
+    bool writeEventToProtoIfMatched(const LogEvent& event, const SimpleAtomMatcher& matcher,
+                                    const sp<UidMap>& uidMap);
+
+    void clearCache();
+
+    void triggerFdFlush();
+
+    void triggerCallback(StatsSubscriptionCallbackReason reason);
+
     const int32_t DEFAULT_PULL_UID = AID_SYSTEM;
 
-    sp<UidMap> mUidMap;
+    const sp<UidMap> mUidMap;
 
-    sp<StatsPullerManager> mPullerMgr;
-
-    android::base::unique_fd mDupIn;
+    const sp<StatsPullerManager> mPullerMgr;
 
     android::base::unique_fd mDupOut;
 
-    android::util::ProtoOutputStream mProto;
-
-    std::vector<SimpleAtomMatcher> mPushedMatchers;
+    const std::vector<SimpleAtomMatcher> mPushedMatchers;
 
     std::vector<PullInfo> mPulledInfo;
 
-    int64_t mTimeoutSec;
+    std::shared_ptr<IStatsSubscriptionCallback> mCallback;
 
-    int64_t mStartTimeSec;
+    const int64_t mTimeoutSec;
+
+    const int64_t mStartTimeSec;
 
     bool mClientAlive = true;
 
-    int64_t mLastWriteMs = 0;
+    int64_t mLastWriteMs;
+
+    // Stores Atom proto messages for events along with their respective timestamps.
+    ProtoOutputStream mProtoOut;
+
+    // Stores the total approximate encoded proto byte-size for cached Atom events in
+    // mEventTimestampNs and mProtoOut.
+    size_t mCacheSize;
 
     static constexpr int64_t kMsBetweenHeartbeats = 1000;
 
     // Cap the buffer size of configs to guard against bad allocations
     static constexpr size_t kMaxSizeKb = 50;
+
+    static constexpr size_t kMaxCacheSizeBytes = 2 * 1024;  // 2 KB
+
+    static constexpr int64_t kMsBetweenCallbacks = 70'000;  // 70 seconds.
 };
 
 }  // namespace statsd
diff --git a/statsd/src/shell/shell_config.proto b/statsd/src/shell/shell_config.proto
index 9c18c4f..6b2890c 100644
--- a/statsd/src/shell/shell_config.proto
+++ b/statsd/src/shell/shell_config.proto
@@ -27,7 +27,7 @@
     optional SimpleAtomMatcher matcher = 1;
 
     /* gap between two pulls in milliseconds */
-    optional int32 freq_millis = 2;
+    optional int64 freq_millis = 2;
 
     /* Packages that the pull is requested from */
     repeated string packages = 3;
@@ -36,4 +36,4 @@
 message ShellSubscription {
     repeated SimpleAtomMatcher pushed = 1;
     repeated PulledAtomSubscription pulled = 2;
-}
\ No newline at end of file
+}
diff --git a/statsd/src/shell/shell_data.proto b/statsd/src/shell/shell_data.proto
index ec41cbc..fdfbcc3 100644
--- a/statsd/src/shell/shell_data.proto
+++ b/statsd/src/shell/shell_data.proto
@@ -26,4 +26,5 @@
 // The output of shell subscription, including both pulled and pushed subscriptions.
 message ShellData {
     repeated Atom atom = 1;
+    repeated int64 elapsed_timestamp_nanos = 2 [packed = true];
 }
diff --git a/statsd/src/socket/LogEventFilter.h b/statsd/src/socket/LogEventFilter.h
new file mode 100644
index 0000000..aec91ef
--- /dev/null
+++ b/statsd/src/socket/LogEventFilter.h
@@ -0,0 +1,133 @@
+/*
+ * 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 <gtest/gtest_prod.h>
+
+#include <atomic>
+#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Templating is for benchmarks only
+ *
+ * Based on benchmarks the more fast container to be used for atom ids filtering
+ * is unordered_set<int>
+ * #BM_LogEventFilterUnorderedSet                       391208 ns     390086 ns         1793
+ * #BM_LogEventFilterUnorderedSet2Consumers            1293527 ns    1289326 ns          543
+ * #BM_LogEventFilterSet                                613362 ns     611259 ns         1146
+ * #BM_LogEventFilterSet2Consumers                     1859397 ns    1854193 ns          378
+ *
+ * See @LogEventFilter definition below
+ */
+template <typename T>
+class LogEventFilterGeneric {
+public:
+    virtual ~LogEventFilterGeneric() = default;
+
+    virtual void setFilteringEnabled(bool isEnabled) {
+        mLogsFilteringEnabled = isEnabled;
+    }
+
+    bool getFilteringEnabled() const {
+        return mLogsFilteringEnabled;
+    }
+
+    /**
+     * @brief Tests atom id with list of interesting atoms
+     *        If Logs filtering is disabled - assume all atoms in use
+     *        Most of the time should be non-blocking call - only in case when setAtomIds() was
+     *        called the call will be blocking due to atom list needs to be synced up
+     * @param atomId
+     * @return true if atom is used by any of consumer or filtering is disabled
+     */
+    virtual bool isAtomInUse(int atomId) const {
+        if (!mLogsFilteringEnabled) {
+            return true;
+        }
+
+        // check if there is an updated set of interesting atom ids
+        if (mLocalSetUpdateCounter != mSetUpdateCounter.load(std::memory_order_relaxed)) {
+            std::lock_guard<std::mutex> guard(mTagIdsMutex);
+            mLocalSetUpdateCounter = mSetUpdateCounter.load(std::memory_order_relaxed);
+            mLocalTagIds.swap(mTagIds);
+        }
+        return mLocalTagIds.find(atomId) != mLocalTagIds.end();
+    }
+
+    typedef const void* ConsumerId;
+
+    typedef T AtomIdSet;
+    /**
+     * @brief Set the Atom Ids object
+     *
+     * @param tagIds set of atoms ids
+     * @param consumer used to differentiate the consumers to form proper superset of ids
+     */
+    virtual void setAtomIds(AtomIdSet tagIds, ConsumerId consumer) {
+        std::lock_guard lock(mTagIdsMutex);
+        // update ids list from consumer
+        if (tagIds.size() == 0) {
+            mTagIdsPerConsumer.erase(consumer);
+        } else {
+            mTagIdsPerConsumer[consumer].swap(tagIds);
+        }
+        // populate the superset incorporating list of distinct atom ids from all consumers
+        mTagIds.clear();
+        for (const auto& [_, atomIds] : mTagIdsPerConsumer) {
+            mTagIds.insert(atomIds.begin(), atomIds.end());
+        }
+        mSetUpdateCounter.fetch_add(1, std::memory_order_relaxed);
+    }
+
+private:
+    std::atomic_bool mLogsFilteringEnabled = true;
+    std::atomic_int mSetUpdateCounter;
+    mutable int mLocalSetUpdateCounter;
+
+    mutable std::mutex mTagIdsMutex;
+    std::unordered_map<ConsumerId, AtomIdSet> mTagIdsPerConsumer;
+    mutable AtomIdSet mTagIds;
+    mutable AtomIdSet mLocalTagIds;
+
+    friend class LogEventFilterTest;
+
+    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);
+};
+
+typedef LogEventFilterGeneric<std::unordered_set<int>> LogEventFilter;
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/statsd/src/socket/StatsSocketListener.cpp b/statsd/src/socket/StatsSocketListener.cpp
index 5764753..5873df3 100644
--- a/statsd/src/socket/StatsSocketListener.cpp
+++ b/statsd/src/socket/StatsSocketListener.cpp
@@ -36,11 +36,11 @@
 namespace os {
 namespace statsd {
 
-StatsSocketListener::StatsSocketListener(std::shared_ptr<LogEventQueue> queue)
-    : SocketListener(getLogSocket(), false /*start listen*/), mQueue(std::move(queue)) {
-}
-
-StatsSocketListener::~StatsSocketListener() {
+StatsSocketListener::StatsSocketListener(std::shared_ptr<LogEventQueue> queue,
+                                         const std::shared_ptr<LogEventFilter>& logEventFilter)
+    : SocketListener(getLogSocket(), false /*start listen*/),
+      mQueue(std::move(queue)),
+      mLogEventFilter(logEventFilter) {
 }
 
 bool StatsSocketListener::onDataAvailable(SocketClient* cli) {
@@ -120,22 +120,38 @@
     }
 
     // move past the 4-byte StatsEventTag
-    uint8_t* msg = ptr + sizeof(uint32_t);
-    uint32_t len = n - sizeof(uint32_t);
-    uint32_t uid = cred->uid;
-    uint32_t pid = cred->pid;
+    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;
 
-    int64_t oldestTimestamp;
-    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(uid, pid);
-    logEvent->parseBuffer(msg, len);
-    const int32_t atomId = logEvent->GetTagId();
-    if (!mQueue->push(std::move(logEvent), &oldestTimestamp)) {
-        StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp, atomId);
-    }
+    processMessage(msg, len, uid, pid, mQueue, mLogEventFilter);
 
     return true;
 }
 
+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) {
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(uid, pid);
+
+    if (filter && filter->getFilteringEnabled()) {
+        const LogEvent::BodyBufferInfo bodyInfo = logEvent->parseHeader(msg, len);
+        if (filter->isAtomInUse(logEvent->GetTagId())) {
+            logEvent->parseBody(bodyInfo);
+        }
+    } else {
+        logEvent->parseBuffer(msg, len);
+    }
+
+    const int32_t atomId = logEvent->GetTagId();
+    const bool isAtomSkipped = logEvent->isParsedHeaderOnly();
+    int64_t oldestTimestamp;
+    if (!queue->push(std::move(logEvent), &oldestTimestamp)) {
+        StatsdStats::getInstance().noteEventQueueOverflow(oldestTimestamp, atomId, isAtomSkipped);
+    }
+}
+
 int StatsSocketListener::getLogSocket() {
     static const char socketName[] = "statsdw";
     int sock = android_get_control_socket(socketName);
diff --git a/statsd/src/socket/StatsSocketListener.h b/statsd/src/socket/StatsSocketListener.h
index 43822f3..74a338e 100644
--- a/statsd/src/socket/StatsSocketListener.h
+++ b/statsd/src/socket/StatsSocketListener.h
@@ -15,8 +15,11 @@
  */
 #pragma once
 
+#include <gtest/gtest_prod.h>
 #include <sysutils/SocketListener.h>
 #include <utils/RefBase.h>
+
+#include "LogEventFilter.h"
 #include "logd/LogEventQueue.h"
 
 // DEFAULT_OVERFLOWUID is defined in linux/highuid.h, which is not part of
@@ -35,20 +38,53 @@
 
 class StatsSocketListener : public SocketListener, public virtual RefBase {
 public:
-    explicit StatsSocketListener(std::shared_ptr<LogEventQueue> queue);
+    explicit StatsSocketListener(std::shared_ptr<LogEventQueue> queue,
+                                 const std::shared_ptr<LogEventFilter>& logEventFilter);
 
-    virtual ~StatsSocketListener();
+    virtual ~StatsSocketListener() = default;
 
 protected:
-    virtual bool onDataAvailable(SocketClient* cli);
+    bool onDataAvailable(SocketClient* cli) override;
 
 private:
     static int getLogSocket();
+
+    /**
+     * @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
+     *
+     * @param msg 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 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);
+
     /**
      * Who is going to get the events when they're read.
      */
     std::shared_ptr<LogEventQueue> mQueue;
+
+    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_TEST(SocketParseMessageTestNoFiltering, TestProcessMessageNoFiltering);
+    FRIEND_TEST(SocketParseMessageTestNoFiltering,
+                TestProcessMessageNoFilteringWithEmptySetExplicitSet);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterEmptySet);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterCompleteSet);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterPartialSet);
+    FRIEND_TEST(SocketParseMessageTest, TestProcessMessageFilterToggle);
 };
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/state/StateManager.cpp b/statsd/src/state/StateManager.cpp
index c488d04..e75bd97 100644
--- a/statsd/src/state/StateManager.cpp
+++ b/statsd/src/state/StateManager.cpp
@@ -108,6 +108,12 @@
     }
 }
 
+void StateManager::addAllAtomIds(LogEventFilter::AtomIdSet& allIds) const {
+    for (const auto& stateTracker : mStateTrackers) {
+        allIds.insert(stateTracker.first);
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/src/state/StateManager.h b/statsd/src/state/StateManager.h
index 8437279..68b5c90 100644
--- a/statsd/src/state/StateManager.h
+++ b/statsd/src/state/StateManager.h
@@ -24,6 +24,7 @@
 
 #include "HashableDimensionKey.h"
 #include "packages/UidMap.h"
+#include "socket/LogEventFilter.h"
 #include "state/StateListener.h"
 #include "state/StateTracker.h"
 
@@ -84,6 +85,8 @@
         return -1;
     }
 
+    void addAllAtomIds(LogEventFilter::AtomIdSet& allIds) const;
+
 private:
     mutable std::mutex mMutex;
 
diff --git a/statsd/src/stats_log.proto b/statsd/src/stats_log.proto
index a820197..44add93 100644
--- a/statsd/src/stats_log.proto
+++ b/statsd/src/stats_log.proto
@@ -504,6 +504,12 @@
         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 ConfigStats config_stats = 3;
@@ -513,6 +519,7 @@
         optional int32 count = 2;
         optional int32 error_count = 3;
         optional int32 dropped_count = 4;
+        optional int32 skip_count = 5;
     }
 
     repeated AtomStats atom_stats = 7;
@@ -622,6 +629,42 @@
     }
 
     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;
+
+      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;
+      }
+
+      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;
 }
 
 message AlertTriggerDetails {
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/statsd_config.proto b/statsd/src/statsd_config.proto
index 758a8d1..36ca44f 100644
--- a/statsd/src/statsd_config.proto
+++ b/statsd/src/statsd_config.proto
@@ -591,6 +591,8 @@
 
   optional uint32 package_certificate_hash_size_bytes = 26;
 
+  optional string restricted_metrics_delegate_package_name = 27;
+
   // 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..a0d13f9 100644
--- a/statsd/src/storage/StorageManager.cpp
+++ b/statsd/src/storage/StorageManager.cpp
@@ -17,19 +17,25 @@
 #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 <android-modules-utils/sdk_level.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 {
 
+using android::modules::sdklevel::IsAtLeastU;
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_MESSAGE;
 using std::map;
@@ -118,6 +124,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 +823,49 @@
             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) {
+            // Remove file if stat fails.
+            remove(fullPathName.c_str());
+            continue;
+        }
+        StatsdStats::getInstance().noteRestrictedConfigDbSize(key, currWallClockSec,
+                                                              fileInfo.st_size);
+        if (fileInfo.st_mtime <= deleteThresholdSec || fileInfo.st_size >= maxBytes) {
+            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..9a25bf0 100644
--- a/statsd/src/storage/StorageManager.h
+++ b/statsd/src/storage/StorageManager.h
@@ -162,6 +162,11 @@
 
     static void sortFiles(vector<FileInfo>* fileNames);
 
+    static void enforceDbGuardrails(const char* path, const int64_t wallClockSec,
+                                    const 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..8a561e6
--- /dev/null
+++ b/statsd/src/utils/DbUtils.h
@@ -0,0 +1,89 @@
+/*
+ * 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, const 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, const int64_t metricId, const LogEvent& event);
+
+/* Deletes a data table for the specified metric. */
+bool deleteTable(const ConfigKey& key, const 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, const int64_t metricId, const vector<LogEvent>& events,
+            string& error);
+
+/* Inserts new data into the specified sqlite db handle. */
+bool insert(sqlite3* db, const 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, const int64_t metricId, const 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
\ No newline at end of file
diff --git a/statsd/src/utils/RestrictedPolicyManager.cpp b/statsd/src/utils/RestrictedPolicyManager.cpp
new file mode 100644
index 0000000..3ae75a4
--- /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) {
+    return mRestrictionCategoryTtlInDaysMap.find(categoryId) !=
+                           mRestrictionCategoryTtlInDaysMap.end()
+                   ? mRestrictionCategoryTtlInDaysMap.at(categoryId)
+                   : DEFAULT_RESTRICTED_CATEGORY_TTL_DAYS;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/statsd/src/utils/RestrictedPolicyManager.h b/statsd/src/utils/RestrictedPolicyManager.h
new file mode 100644
index 0000000..34d1fa3
--- /dev/null
+++ b/statsd/src/utils/RestrictedPolicyManager.h
@@ -0,0 +1,57 @@
+/*
+ * 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"
+
+using std::map;
+
+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 {
+public:
+    static RestrictedPolicyManager& getInstance();
+    ~RestrictedPolicyManager(){};
+
+    // Gets the TTL in days for a particular restricted category. Returns the default for unknown
+    // categories.
+    int32_t getRestrictedCategoryTtl(const StatsdRestrictionCategory categoryId);
+
+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 6d847df..4a0df97 100644
--- a/statsd/src/utils/ShardOffsetProvider.cpp
+++ b/statsd/src/utils/ShardOffsetProvider.cpp
@@ -16,15 +16,31 @@
 
 #include "ShardOffsetProvider.h"
 
+#include <errno.h>
+#include <sys/random.h>
+#include <unistd.h>
+
+#include <chrono>
+
 namespace android {
 namespace os {
 namespace statsd {
 
-ShardOffsetProvider::ShardOffsetProvider(const int shardOffset) : mShardOffset(shardOffset) {
+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::getInstance() {
-    static ShardOffsetProvider sShardOffsetProvider(rand());
+    static ShardOffsetProvider sShardOffsetProvider;
     return sShardOffsetProvider;
 }
 
diff --git a/statsd/src/utils/ShardOffsetProvider.h b/statsd/src/utils/ShardOffsetProvider.h
index 0623e0f..dd6a5a7 100644
--- a/statsd/src/utils/ShardOffsetProvider.h
+++ b/statsd/src/utils/ShardOffsetProvider.h
@@ -32,21 +32,21 @@
 public:
     ~ShardOffsetProvider(){};
 
-    int getShardOffset() const {
+    uint32_t getShardOffset() const {
         return mShardOffset;
     }
 
     static ShardOffsetProvider& getInstance();
 
 private:
-    ShardOffsetProvider(const int shardOffset);
+    ShardOffsetProvider();
 
     // Only used for testing.
-    void setShardOffset(const int shardOffset) {
+    void setShardOffset(const uint32_t shardOffset) {
         mShardOffset = shardOffset;
     }
 
-    int mShardOffset;
+    uint32_t mShardOffset;
 
     FRIEND_TEST(CountMetricE2eTest, TestDimensionalSampling);
     FRIEND_TEST(DurationMetricE2eTest, TestDimensionalSampling);
@@ -54,6 +54,7 @@
     FRIEND_TEST(GaugeMetricProducerTest, TestPullDimensionalSampling);
     FRIEND_TEST(KllMetricE2eTest, TestDimensionalSampling);
     FRIEND_TEST(NumericValueMetricProducerTest, TestDimensionalSampling);
+    FRIEND_TEST(StatsdStatsTest, TestShardOffsetProvider);
 };
 
 }  // namespace statsd
diff --git a/statsd/tests/ConfigManager_test.cpp b/statsd/tests/ConfigManager_test.cpp
index bfc8968..f03ccf5 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/LogEntryMatcher_test.cpp b/statsd/tests/LogEntryMatcher_test.cpp
index d0a063f..c91a443 100644
--- a/statsd/tests/LogEntryMatcher_test.cpp
+++ b/statsd/tests/LogEntryMatcher_test.cpp
@@ -15,9 +15,9 @@
 #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"
@@ -120,7 +120,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);
 }
 
@@ -412,7 +412,8 @@
 
     // 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);
@@ -1231,12 +1232,12 @@
 
     // 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);
+    makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID, 1111, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
 
     // 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);
+    makeIntWithBoolAnnotationLogEvent(&event3, TAG_ID, 3333, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event3));
 
     // Update matcher to match one AID
@@ -1245,12 +1246,12 @@
 
     // Event where mapping from uid to aid doesn't fit wildcard pattern.
     LogEvent event4(/*uid=*/0, /*pid=*/0);
-    makeIntWithBoolAnnotationLogEvent(&event4, TAG_ID, 1005, ANNOTATION_ID_IS_UID, true);
+    makeIntWithBoolAnnotationLogEvent(&event4, TAG_ID, 1005, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event4));
 
     // 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);
+    makeIntWithBoolAnnotationLogEvent(&event5, TAG_ID, 1000, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event5));
 
     // Update matcher to match multiple AIDs
@@ -1258,16 +1259,16 @@
 
     // Event where mapping from uid to aid doesn't fit wildcard pattern.
     LogEvent event6(/*uid=*/0, /*pid=*/0);
-    makeIntWithBoolAnnotationLogEvent(&event6, TAG_ID, 1036, ANNOTATION_ID_IS_UID, true);
+    makeIntWithBoolAnnotationLogEvent(&event6, TAG_ID, 1036, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event6));
 
     // 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);
+    makeIntWithBoolAnnotationLogEvent(&event7, TAG_ID, 1034, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event7));
 
     LogEvent event8(/*uid=*/0, /*pid=*/0);
-    makeIntWithBoolAnnotationLogEvent(&event8, TAG_ID, 1035, ANNOTATION_ID_IS_UID, true);
+    makeIntWithBoolAnnotationLogEvent(&event8, TAG_ID, 1035, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event8));
 }
 
diff --git a/statsd/tests/LogEventFilter_test.cpp b/statsd/tests/LogEventFilter_test.cpp
new file mode 100644
index 0000000..037f02d
--- /dev/null
+++ b/statsd/tests/LogEventFilter_test.cpp
@@ -0,0 +1,191 @@
+/*
+ * 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 "socket/LogEventFilter.h"
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+constexpr int kAtomIdsCount = 100;  //  Filter size setup
+
+LogEventFilter::AtomIdSet generateAtomIds(int rangeStart, int rangeEndInclusive) {
+    LogEventFilter::AtomIdSet atomIds;
+    for (int i = rangeStart; i <= rangeEndInclusive; ++i) {
+        atomIds.insert(i);
+    }
+    return atomIds;
+}
+
+bool testGuaranteedUnusedAtomsNotInUse(const LogEventFilter& filter) {
+    const auto sampleIds = generateAtomIds(10000, 11000);
+    bool atLeastOneInUse = false;
+    for (const auto& atomId : sampleIds) {
+        atLeastOneInUse |= filter.isAtomInUse(atomId);
+    }
+    return !atLeastOneInUse;
+}
+
+}  // namespace
+
+TEST(LogEventFilterTest, TestEmptyFilter) {
+    LogEventFilter filter;
+    const auto sampleIds = generateAtomIds(1, kAtomIdsCount);
+    for (const auto& atomId : sampleIds) {
+        EXPECT_FALSE(filter.isAtomInUse(atomId));
+    }
+}
+
+TEST(LogEventFilterTest, TestRemoveNonExistingEmptyFilter) {
+    LogEventFilter filter;
+    EXPECT_FALSE(filter.isAtomInUse(1));
+    LogEventFilter::AtomIdSet emptyAtomIdsSet;
+    EXPECT_EQ(0, filter.mTagIdsPerConsumer.size());
+    EXPECT_EQ(0, filter.mLocalTagIds.size());
+    filter.setAtomIds(std::move(emptyAtomIdsSet), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+    EXPECT_FALSE(filter.isAtomInUse(1));
+    EXPECT_EQ(0, filter.mLocalTagIds.size());
+    EXPECT_EQ(0, filter.mTagIdsPerConsumer.size());
+}
+
+TEST(LogEventFilterTest, TestEmptyFilterDisabled) {
+    LogEventFilter filter;
+    filter.setFilteringEnabled(false);
+    const auto sampleIds = generateAtomIds(1, kAtomIdsCount);
+    for (const auto& atomId : sampleIds) {
+        EXPECT_TRUE(filter.isAtomInUse(atomId));
+    }
+}
+
+TEST(LogEventFilterTest, TestNonEmptyFilterFullOverlap) {
+    LogEventFilter filter;
+    auto filterIds = generateAtomIds(1, kAtomIdsCount);
+    filter.setAtomIds(std::move(filterIds), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+    EXPECT_EQ(1, filter.mTagIdsPerConsumer.size());
+
+    // inner copy updated only during fetch if required
+    EXPECT_EQ(0, filter.mLocalTagIds.size());
+    const auto sampleIds = generateAtomIds(1, kAtomIdsCount);
+    for (const auto& atomId : sampleIds) {
+        EXPECT_TRUE(filter.isAtomInUse(atomId));
+    }
+    EXPECT_EQ(kAtomIdsCount, filter.mLocalTagIds.size());
+}
+
+TEST(LogEventFilterTest, TestNonEmptyFilterPartialOverlap) {
+    LogEventFilter filter;
+    auto filterIds = generateAtomIds(1, kAtomIdsCount);
+    filter.setAtomIds(std::move(filterIds), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+    // extra 100 atom ids should be filtered out
+    const auto sampleIds = generateAtomIds(1, kAtomIdsCount + 100);
+    for (const auto& atomId : sampleIds) {
+        bool const atomInUse = atomId <= kAtomIdsCount;
+        EXPECT_EQ(atomInUse, filter.isAtomInUse(atomId));
+    }
+}
+
+TEST(LogEventFilterTest, TestNonEmptyFilterDisabledPartialOverlap) {
+    LogEventFilter filter;
+    auto filterIds = generateAtomIds(1, kAtomIdsCount);
+    filter.setAtomIds(std::move(filterIds), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+    filter.setFilteringEnabled(false);
+    // extra 100 atom ids should be in use due to filter is disabled
+    const auto sampleIds = generateAtomIds(1, kAtomIdsCount + 100);
+    for (const auto& atomId : sampleIds) {
+        EXPECT_TRUE(filter.isAtomInUse(atomId));
+    }
+}
+
+TEST(LogEventFilterTest, TestMultipleConsumerOverlapIdsRemoved) {
+    LogEventFilter filter;
+    auto filterIds1 = generateAtomIds(1, kAtomIdsCount);
+    // half of filterIds1 atom ids overlaps with filterIds2
+    auto filterIds2 = generateAtomIds(kAtomIdsCount / 2, kAtomIdsCount * 2);
+    filter.setAtomIds(std::move(filterIds1), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+    filter.setAtomIds(std::move(filterIds2), reinterpret_cast<LogEventFilter::ConsumerId>(1));
+    // inner copy updated only during fetch if required
+    EXPECT_EQ(0, filter.mLocalTagIds.size());
+    const auto sampleIds = generateAtomIds(1, kAtomIdsCount * 2);
+    for (const auto& atomId : sampleIds) {
+        EXPECT_TRUE(filter.isAtomInUse(atomId));
+    }
+    EXPECT_EQ(kAtomIdsCount * 2, filter.mLocalTagIds.size());
+    EXPECT_TRUE(testGuaranteedUnusedAtomsNotInUse(filter));
+
+    // set empty filter for second consumer
+    LogEventFilter::AtomIdSet emptyAtomIdsSet;
+    filter.setAtomIds(std::move(emptyAtomIdsSet), reinterpret_cast<LogEventFilter::ConsumerId>(1));
+    EXPECT_EQ(kAtomIdsCount * 2, filter.mLocalTagIds.size());
+    for (const auto& atomId : sampleIds) {
+        bool const atomInUse = atomId <= kAtomIdsCount;
+        EXPECT_EQ(atomInUse, filter.isAtomInUse(atomId));
+    }
+    EXPECT_EQ(kAtomIdsCount, filter.mLocalTagIds.size());
+    EXPECT_TRUE(testGuaranteedUnusedAtomsNotInUse(filter));
+}
+
+TEST(LogEventFilterTest, TestMultipleConsumerEmptyFilter) {
+    LogEventFilter filter;
+    auto filterIds1 = generateAtomIds(1, kAtomIdsCount);
+    auto filterIds2 = generateAtomIds(kAtomIdsCount + 1, kAtomIdsCount * 2);
+    filter.setAtomIds(std::move(filterIds1), reinterpret_cast<LogEventFilter::ConsumerId>(0));
+    filter.setAtomIds(std::move(filterIds2), reinterpret_cast<LogEventFilter::ConsumerId>(1));
+    EXPECT_EQ(2, filter.mTagIdsPerConsumer.size());
+    // inner copy updated only during fetch if required
+    EXPECT_EQ(0, filter.mLocalTagIds.size());
+    const auto sampleIds = generateAtomIds(1, kAtomIdsCount * 2);
+    for (const auto& atomId : sampleIds) {
+        EXPECT_TRUE(filter.isAtomInUse(atomId));
+    }
+    EXPECT_EQ(kAtomIdsCount * 2, filter.mLocalTagIds.size());
+    EXPECT_TRUE(testGuaranteedUnusedAtomsNotInUse(filter));
+
+    // set empty filter for first consumer
+    LogEventFilter::AtomIdSet emptyAtomIdsSet;
+    filter.setAtomIds(emptyAtomIdsSet, reinterpret_cast<LogEventFilter::ConsumerId>(0));
+    EXPECT_EQ(1, filter.mTagIdsPerConsumer.size());
+    EXPECT_EQ(kAtomIdsCount * 2, filter.mLocalTagIds.size());
+    for (const auto& atomId : sampleIds) {
+        bool const atomInUse = atomId > kAtomIdsCount;
+        EXPECT_EQ(atomInUse, filter.isAtomInUse(atomId));
+    }
+    EXPECT_EQ(kAtomIdsCount, filter.mLocalTagIds.size());
+    EXPECT_TRUE(testGuaranteedUnusedAtomsNotInUse(filter));
+
+    // set empty filter for second consumer
+    filter.setAtomIds(emptyAtomIdsSet, reinterpret_cast<LogEventFilter::ConsumerId>(1));
+    EXPECT_EQ(0, filter.mTagIdsPerConsumer.size());
+    EXPECT_EQ(kAtomIdsCount, filter.mLocalTagIds.size());
+    for (const auto& atomId : sampleIds) {
+        EXPECT_FALSE(filter.isAtomInUse(atomId));
+    }
+    EXPECT_EQ(0, filter.mLocalTagIds.size());
+    EXPECT_TRUE(testGuaranteedUnusedAtomsNotInUse(filter));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/statsd/tests/LogEvent_test.cpp b/statsd/tests/LogEvent_test.cpp
index f0d090d..1a9b920 100644
--- a/statsd/tests/LogEvent_test.cpp
+++ b/statsd/tests/LogEvent_test.cpp
@@ -14,12 +14,16 @@
 
 #include "src/logd/LogEvent.h"
 
+#include <android-modules-utils/sdk_level.h>
 #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"
 
 #ifdef __ANDROID__
 
@@ -27,10 +31,11 @@
 namespace os {
 namespace statsd {
 
+using android::modules::sdklevel::IsAtLeastU;
 using std::string;
 using std::vector;
-using util::ProtoOutputStream;
-using util::ProtoReader;
+using ::util::ProtoOutputStream;
+using ::util::ProtoReader;
 
 namespace {
 
@@ -43,94 +48,117 @@
     return f;
 }
 
-void createStatsEvent(AStatsEvent* statsEvent, uint8_t typeId) {
-    AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
-
-    int int32Array[2] = {3, 6};
-    uint32_t uids[] = {1001, 1002};
-    const char* tags[] = {"tag1", "tag2"};
-
-    switch (typeId) {
-        case INT32_TYPE:
-            AStatsEvent_writeInt32(statsEvent, 10);
-            break;
-        case INT64_TYPE:
-            AStatsEvent_writeInt64(statsEvent, 1000L);
-            break;
-        case STRING_TYPE:
-            AStatsEvent_writeString(statsEvent, "test");
-            break;
-        case LIST_TYPE:
-            AStatsEvent_writeInt32Array(statsEvent, int32Array, 2);
-            break;
-        case FLOAT_TYPE:
-            AStatsEvent_writeFloat(statsEvent, 1.3f);
-            break;
-        case BOOL_TYPE:
-            AStatsEvent_writeBool(statsEvent, 1);
-            break;
-        case BYTE_ARRAY_TYPE:
-            AStatsEvent_writeByteArray(statsEvent, (uint8_t*)"test", strlen("test"));
-            break;
-        case ATTRIBUTION_CHAIN_TYPE:
-            AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
-            break;
-        default:
-            break;
-    }
-}
-
-void createFieldWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
-                                           bool annotationValue, bool parseBufferResult) {
+bool createFieldWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
+                                           bool annotationValue, bool doHeaderPrefetch) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
-    createStatsEvent(statsEvent, typeId);
+    createStatsEvent(statsEvent, typeId, /*atomId=*/100);
     AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
     AStatsEvent_build(statsEvent);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    EXPECT_EQ(parseBufferResult, logEvent->parseBuffer(buf, 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();
 }
 
-void createFieldWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
-                                          int annotationValue, bool parseBufferResult) {
+bool createFieldWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
+                                          int annotationValue, bool doHeaderPrefetch) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
-    createStatsEvent(statsEvent, typeId);
+    createStatsEvent(statsEvent, typeId, /*atomId=*/100);
     AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue);
     AStatsEvent_build(statsEvent);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    EXPECT_EQ(parseBufferResult, logEvent->parseBuffer(buf, 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 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.
-class LogEventTestBadAnnotationFieldTypes : public testing::TestWithParam<int> {
+class LogEventTestBadAnnotationFieldTypes : public testing::TestWithParam<std::tuple<int, bool>> {
 public:
-    static std::string ToString(testing::TestParamInfo<int> info) {
-        switch (info.param) {
+    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 INT32_TYPE:
-                return "Int32";
+                return "Int32" + boolName;
             case INT64_TYPE:
-                return "Int64";
+                return "Int64" + boolName;
             case STRING_TYPE:
-                return "String";
+                return "String" + boolName;
             case LIST_TYPE:
-                return "List";
+                return "List" + boolName;
             case FLOAT_TYPE:
-                return "Float";
+                return "Float" + boolName;
             case BYTE_ARRAY_TYPE:
-                return "ByteArray";
+                return "ByteArray" + boolName;
             case ATTRIBUTION_CHAIN_TYPE:
-                return "AttributionChain";
+                return "AttributionChain" + boolName;
             default:
-                return "Unknown";
+                return "Unknown" + boolName;
         }
     }
 };
@@ -138,11 +166,40 @@
 // 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(BadAnnotationFieldTypes, LogEventTestBadAnnotationFieldTypes,
-                         testing::Values(INT32_TYPE, INT64_TYPE, STRING_TYPE, LIST_TYPE, FLOAT_TYPE,
-                                         BYTE_ARRAY_TYPE, ATTRIBUTION_CHAIN_TYPE),
+                         testing::Combine(testing::Values(INT32_TYPE, INT64_TYPE, STRING_TYPE,
+                                                          LIST_TYPE, FLOAT_TYPE, BYTE_ARRAY_TYPE,
+                                                          ATTRIBUTION_CHAIN_TYPE),
+                                          testing::Bool()),
                          LogEventTestBadAnnotationFieldTypes::ToString);
 
-TEST(LogEventTest, TestPrimitiveParsing) {
+class LogEventTest : public testing::TestWithParam<bool> {
+public:
+    bool ParseBuffer(LogEvent& logEvent, const uint8_t* buf, size_t size) {
+        size_t bufferOffset = 0;
+        if (GetParam()) {
+            // Testing LogEvent header prefetch logic
+            const LogEvent::BodyBufferInfo bodyInfo = logEvent.parseHeader(buf, size);
+            EXPECT_TRUE(logEvent.isParsedHeaderOnly());
+            const bool parseResult = logEvent.parseBody(bodyInfo);
+            EXPECT_EQ(parseResult, logEvent.isValid());
+            EXPECT_FALSE(logEvent.isParsedHeaderOnly());
+        } else {
+            const bool parseResult = logEvent.parseBuffer(buf, size);
+            EXPECT_EQ(parseResult, logEvent.isValid());
+            EXPECT_FALSE(logEvent.isParsedHeaderOnly());
+        }
+        return logEvent.isValid();
+    }
+
+    static std::string ToString(testing::TestParamInfo<bool> info) {
+        return info.param ? "PrefetchTrue" : "PrefetchFalse";
+    }
+};
+
+INSTANTIATE_TEST_SUITE_P(LogEventTestBufferParsing, LogEventTest, testing::Bool(),
+                         LogEventTest::ToString);
+
+TEST_P(LogEventTest, TestPrimitiveParsing) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
     AStatsEvent_writeInt32(event, 10);
@@ -152,10 +209,10 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
 
     EXPECT_EQ(100, logEvent.GetTagId());
     EXPECT_EQ(1000, logEvent.GetUid());
@@ -192,7 +249,33 @@
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestFetchHeaderOnly) {
+TEST_P(LogEventTest, TestEventWithInvalidHeaderParsing) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32(event, 10);
+    AStatsEvent_writeInt64(event, 0x123456789);
+    AStatsEvent_writeFloat(event, 2.0);
+    AStatsEvent_writeBool(event, true);
+    AStatsEvent_build(event);
+
+    size_t size;
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+
+    // Corrupt LogEvent header info
+    // OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID
+    // Corrupting first 4 bytes will be sufficient
+    uint8_t* bufMod = const_cast<uint8_t*>(buf);
+    memset(static_cast<void*>(bufMod), 4, ERROR_TYPE);
+
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
+    EXPECT_FALSE(logEvent.isValid());
+    EXPECT_FALSE(logEvent.isParsedHeaderOnly());
+
+    AStatsEvent_release(event);
+}
+
+TEST(LogEventTestParsing, TestFetchHeaderOnly) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
     AStatsEvent_writeInt32(event, 10);
@@ -205,7 +288,9 @@
     const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size, /*fetchHeaderOnly=*/true));
+    const LogEvent::BodyBufferInfo bodyInfo = logEvent.parseHeader(buf, size);
+    EXPECT_TRUE(logEvent.isValid());
+    EXPECT_TRUE(logEvent.isParsedHeaderOnly());
 
     AStatsEvent_release(event);
 
@@ -213,11 +298,10 @@
     EXPECT_EQ(1000, logEvent.GetUid());
     EXPECT_EQ(1001, logEvent.GetPid());
     EXPECT_FALSE(logEvent.hasAttributionChain());
-
-    ASSERT_EQ(logEvent.getValues().size(), 0);
+    ASSERT_EQ(0, logEvent.getValues().size());
 }
 
-TEST(LogEventTest, TestStringAndByteArrayParsing) {
+TEST_P(LogEventTest, TestStringAndByteArrayParsing) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
     string str = "test";
@@ -226,10 +310,10 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
 
     EXPECT_EQ(100, logEvent.GetTagId());
     EXPECT_EQ(1000, logEvent.GetUid());
@@ -255,7 +339,7 @@
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestEmptyString) {
+TEST_P(LogEventTest, TestEmptyString) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
     string empty = "";
@@ -263,10 +347,10 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
 
     EXPECT_EQ(100, logEvent.GetTagId());
     EXPECT_EQ(1000, logEvent.GetUid());
@@ -285,7 +369,7 @@
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestByteArrayWithNullCharacter) {
+TEST_P(LogEventTest, TestByteArrayWithNullCharacter) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
     uint8_t message[] = {'\t', 'e', '\0', 's', 't'};
@@ -293,10 +377,10 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
 
     EXPECT_EQ(100, logEvent.GetTagId());
     EXPECT_EQ(1000, logEvent.GetUid());
@@ -315,7 +399,7 @@
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestTooManyTopLevelElements) {
+TEST_P(LogEventTest, TestTooManyTopLevelElements) {
     int32_t numElements = 128;
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
@@ -327,14 +411,14 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+    EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
 
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestAttributionChain) {
+TEST_P(LogEventTest, TestAttributionChain) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
 
@@ -348,10 +432,10 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
 
     EXPECT_EQ(100, logEvent.GetTagId());
     EXPECT_EQ(1000, logEvent.GetUid());
@@ -394,7 +478,7 @@
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestEmptyAttributionChain) {
+TEST_P(LogEventTest, TestEmptyAttributionChain) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
 
@@ -403,15 +487,15 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+    EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
 
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestAttributionChainTooManyElements) {
+TEST_P(LogEventTest, TestAttributionChainTooManyElements) {
     int32_t numNodes = 128;
     uint32_t uids[numNodes];
     vector<string> tags(numNodes);  // storage that cTag elements point to
@@ -429,14 +513,14 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+    EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
 
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestArrayParsing) {
+TEST_P(LogEventTest, TestArrayParsing) {
     size_t numElements = 2;
     int32_t int32Array[2] = {3, 6};
     int64_t int64Array[2] = {1000L, 1002L};
@@ -459,10 +543,10 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
 
     EXPECT_EQ(100, logEvent.GetTagId());
     EXPECT_EQ(1000, logEvent.GetUid());
@@ -535,7 +619,7 @@
     EXPECT_EQ("str2", stringArrayItem2.mValue.str_value);
 }
 
-TEST(LogEventTest, TestEmptyStringArray) {
+TEST_P(LogEventTest, TestEmptyStringArray) {
     const char* cStringArray[2];
     string empty = "";
     cStringArray[0] = empty.c_str();
@@ -547,10 +631,10 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
 
     EXPECT_EQ(100, logEvent.GetTagId());
     EXPECT_EQ(1000, logEvent.GetUid());
@@ -574,7 +658,7 @@
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestArrayTooManyElements) {
+TEST_P(LogEventTest, TestArrayTooManyElements) {
     int32_t numElements = 128;
     int32_t int32Array[numElements];
 
@@ -588,15 +672,15 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+    EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
 
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestEmptyArray) {
+TEST_P(LogEventTest, TestEmptyArray) {
     int32_t int32Array[0] = {};
 
     AStatsEvent* event = AStatsEvent_obtain();
@@ -605,10 +689,10 @@
     AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
 
     EXPECT_EQ(100, logEvent.GetTagId());
     EXPECT_EQ(1000, logEvent.GetUid());
@@ -619,10 +703,11 @@
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestAnnotationIdIsUid) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_IS_UID, true,
-                                          /*parseBufferResult*/ true);
+    EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
+                                                      ASTATSLOG_ANNOTATION_ID_IS_UID, true,
+                                                      /*doHeaderPrefetch=*/GetParam()));
 
     ASSERT_EQ(event.getNumUidFields(), 1);
 
@@ -631,7 +716,7 @@
     EXPECT_TRUE(isUidField(values.at(0)));
 }
 
-TEST(LogEventTest, TestAnnotationIdIsUid_RepeatedIntAndOtherFields) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_RepeatedIntAndOtherFields) {
     size_t numElements = 2;
     int32_t int32Array[2] = {3, 6};
 
@@ -645,14 +730,14 @@
     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);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
     EXPECT_EQ(2, logEvent.getNumUidFields());
 
     const vector<FieldValue>& values = logEvent.getValues();
@@ -664,20 +749,20 @@
     EXPECT_FALSE(isUidField(values.at(4)));
 }
 
-TEST(LogEventTest, TestAnnotationIdIsUid_RepeatedIntOneEntry) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_RepeatedIntOneEntry) {
     size_t numElements = 1;
     int32_t int32Array[1] = {3};
 
     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;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
     EXPECT_EQ(1, logEvent.getNumUidFields());
 
     const vector<FieldValue>& values = logEvent.getValues();
@@ -685,46 +770,46 @@
     EXPECT_TRUE(isUidField(values.at(0)));
 }
 
-TEST(LogEventTest, TestAnnotationIdIsUid_EmptyIntArray) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_EmptyIntArray) {
     int32_t int32Array[0] = {};
 
     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);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
     EXPECT_EQ(0, logEvent.getNumUidFields());
 
     const vector<FieldValue>& values = logEvent.getValues();
     EXPECT_EQ(values.size(), 1);
 }
 
-TEST(LogEventTest, TestAnnotationIdIsUid_BadRepeatedInt64) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_BadRepeatedInt64) {
     int64_t int64Array[2] = {1000L, 1002L};
 
     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;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
 
-    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+    EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
     EXPECT_EQ(0, logEvent.getNumUidFields());
 
     AStatsEvent_release(statsEvent);
 }
 
-TEST(LogEventTest, TestAnnotationIdIsUid_BadRepeatedString) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_BadRepeatedString) {
     size_t numElements = 2;
     vector<string> stringArray = {"str1", "str2"};
     const char* cStringArray[2];
@@ -735,14 +820,14 @@
     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;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
 
-    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+    EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
     EXPECT_EQ(0, logEvent.getNumUidFields());
 
     AStatsEvent_release(statsEvent);
@@ -751,22 +836,25 @@
 TEST_P(LogEventTestBadAnnotationFieldTypes, TestAnnotationIdIsUid) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
 
-    if (GetParam() != INT32_TYPE && GetParam() != LIST_TYPE) {
-        createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_IS_UID, true,
-                                              /*parseBufferResult*/ false);
+    if (std::get<0>(GetParam()) != INT32_TYPE && std::get<0>(GetParam()) != LIST_TYPE) {
+        EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_IS_UID, true,
+                /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
 
-TEST(LogEventTest, TestAnnotationIdIsUid_NotIntAnnotation) {
+TEST_P(LogEventTest, TestAnnotationIdIsUid_NotIntAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_IS_UID, 10,
-                                         /*parseBufferResult*/ false);
+    EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
+                                                      ASTATSLOG_ANNOTATION_ID_IS_UID, 10,
+                                                      /*doHeaderPrefetch=*/GetParam()));
 }
 
-TEST(LogEventTest, TestAnnotationIdStateNested) {
+TEST_P(LogEventTest, TestAnnotationIdStateNested) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_STATE_NESTED, true,
-                                          /*parseBufferResult*/ true);
+    EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
+                                                      ASTATSLOG_ANNOTATION_ID_STATE_NESTED, true,
+                                                      /*doHeaderPrefetch=*/GetParam()));
 
     const vector<FieldValue>& values = event.getValues();
     ASSERT_EQ(values.size(), 1);
@@ -776,22 +864,25 @@
 TEST_P(LogEventTestBadAnnotationFieldTypes, TestAnnotationIdStateNested) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
 
-    if (GetParam() != INT32_TYPE) {
-        createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_STATE_NESTED, true,
-                                              /*parseBufferResult*/ false);
+    if (std::get<0>(GetParam()) != INT32_TYPE) {
+        EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_STATE_NESTED, true,
+                /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
 
-TEST(LogEventTest, TestAnnotationIdStateNested_NotIntAnnotation) {
+TEST_P(LogEventTest, TestAnnotationIdStateNested_NotIntAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_STATE_NESTED, 10,
-                                         /*parseBufferResult*/ false);
+    EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
+                                                      ASTATSLOG_ANNOTATION_ID_STATE_NESTED, 10,
+                                                      /*doHeaderPrefetch=*/GetParam()));
 }
 
-TEST(LogEventTest, TestPrimaryFieldAnnotation) {
+TEST_P(LogEventTest, TestPrimaryFieldAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_PRIMARY_FIELD, true,
-                                          /*parseBufferResult*/ true);
+    EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
+                                                      ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true,
+                                                      /*doHeaderPrefetch=*/GetParam()));
 
     const vector<FieldValue>& values = event.getValues();
     ASSERT_EQ(values.size(), 1);
@@ -801,22 +892,25 @@
 TEST_P(LogEventTestBadAnnotationFieldTypes, TestPrimaryFieldAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
 
-    if (GetParam() == LIST_TYPE || GetParam() == ATTRIBUTION_CHAIN_TYPE) {
-        createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_PRIMARY_FIELD, true,
-                                              /*parseBufferResult*/ false);
+    if (std::get<0>(GetParam()) == LIST_TYPE || std::get<0>(GetParam()) == ATTRIBUTION_CHAIN_TYPE) {
+        EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, true,
+                /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
 
-TEST(LogEventTest, TestPrimaryFieldAnnotation_NotIntAnnotation) {
+TEST_P(LogEventTest, TestPrimaryFieldAnnotation_NotIntAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_PRIMARY_FIELD, 10,
-                                         /*parseBufferResult*/ false);
+    EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
+                                                      ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD, 10,
+                                                      /*doHeaderPrefetch=*/GetParam()));
 }
 
-TEST(LogEventTest, TestExclusiveStateAnnotation) {
+TEST_P(LogEventTest, TestExclusiveStateAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_EXCLUSIVE_STATE, true,
-                                          /*parseBufferResult*/ true);
+    EXPECT_TRUE(createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE,
+                                                      ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true,
+                                                      /*doHeaderPrefetch=*/GetParam()));
 
     const vector<FieldValue>& values = event.getValues();
     ASSERT_EQ(values.size(), 1);
@@ -826,20 +920,21 @@
 TEST_P(LogEventTestBadAnnotationFieldTypes, TestExclusiveStateAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
 
-    if (GetParam() != INT32_TYPE) {
-        createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_EXCLUSIVE_STATE,
-                                              true,
-                                              /*parseBufferResult*/ false);
+    if (std::get<0>(GetParam()) != INT32_TYPE) {
+        EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, true,
+                /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
 
-TEST(LogEventTest, TestExclusiveStateAnnotation_NotIntAnnotation) {
+TEST_P(LogEventTest, TestExclusiveStateAnnotation_NotIntAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_EXCLUSIVE_STATE, 10,
-                                         /*parseBufferResult*/ false);
+    EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE,
+                                                      ASTATSLOG_ANNOTATION_ID_EXCLUSIVE_STATE, 10,
+                                                      /*doHeaderPrefetch=*/GetParam()));
 }
 
-TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) {
+TEST_P(LogEventTest, TestPrimaryFieldFirstUidAnnotation) {
     // Event has 10 ints and then an attribution chain
     int numInts = 10;
     int firstUidInChainIndex = numInts;
@@ -855,14 +950,15 @@
         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
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
     LogEvent logEvent(/*uid=*/0, /*pid=*/0);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
     AStatsEvent_release(statsEvent);
 
     // Check annotation
@@ -874,50 +970,88 @@
 TEST_P(LogEventTestBadAnnotationFieldTypes, TestPrimaryFieldFirstUidAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
 
-    if (GetParam() != ATTRIBUTION_CHAIN_TYPE) {
-        createFieldWithBoolAnnotationLogEvent(&event, GetParam(),
-                                              ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true,
-                                              /*parseBufferResult*/ false);
+    if (std::get<0>(GetParam()) != ATTRIBUTION_CHAIN_TYPE) {
+        EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID,
+                true,
+                /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
 
-TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation_NotIntAnnotation) {
+TEST_P(LogEventTest, TestPrimaryFieldFirstUidAnnotation_NotIntAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithIntAnnotationLogEvent(&event, ATTRIBUTION_CHAIN_TYPE,
-                                         ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, 10,
-                                         /*parseBufferResult*/ false);
+    EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(
+            &event, ATTRIBUTION_CHAIN_TYPE, ASTATSLOG_ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, 10,
+            /*doHeaderPrefetch=*/GetParam()));
 }
 
-TEST(LogEventTest, TestResetStateAnnotation) {
+TEST_P(LogEventTest, TestResetStateAnnotation) {
     int32_t resetState = 10;
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_TRIGGER_STATE_RESET,
-                                         resetState, /*parseBufferResult*/ true);
+    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 (GetParam() != INT32_TYPE) {
-        createFieldWithIntAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_TRIGGER_STATE_RESET,
-                                             resetState,
-                                             /*parseBufferResult*/ false);
+    if (std::get<0>(GetParam()) != INT32_TYPE) {
+        EXPECT_FALSE(createFieldWithIntAnnotationLogEvent(
+                &event, std::get<0>(GetParam()), ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET,
+                resetState,
+                /*doHeaderPrefetch=*/std::get<1>(GetParam())));
     }
 }
 
-TEST(LogEventTest, TestResetStateAnnotation_NotBoolAnnotation) {
+TEST_P(LogEventTest, TestResetStateAnnotation_NotBoolAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_TRIGGER_STATE_RESET,
-                                          true,
-                                          /*parseBufferResult*/ false);
+    EXPECT_FALSE(createFieldWithBoolAnnotationLogEvent(
+            &event, INT32_TYPE, ASTATSLOG_ANNOTATION_ID_TRIGGER_STATE_RESET, true,
+            /*doHeaderPrefetch=*/GetParam()));
 }
 
-TEST(LogEventTest, TestUidAnnotationWithInt8MaxValues) {
+TEST_P(LogEventTest, TestUidAnnotationWithInt8MaxValues) {
     int32_t numElements = INT8_MAX;
     int32_t int32Array[numElements];
 
@@ -930,18 +1064,18 @@
     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;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_TRUE(ParseBuffer(logEvent, buf, size));
 
     AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestEmptyAttributionChainWithPrimaryFieldFirstUidAnnotation) {
+TEST_P(LogEventTest, TestEmptyAttributionChainWithPrimaryFieldFirstUidAnnotation) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
 
@@ -950,19 +1084,116 @@
 
     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);
 
     size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    const uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+    EXPECT_FALSE(ParseBuffer(logEvent, buf, size));
 
     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 73e3d3c..16e9ab7 100644
--- a/statsd/tests/MetricsManager_test.cpp
+++ b/statsd/tests/MetricsManager_test.cpp
@@ -36,6 +36,7 @@
 using namespace testing;
 using android::sp;
 using android::modules::sdklevel::IsAtLeastS;
+using android::modules::sdklevel::IsAtLeastU;
 using android::os::statsd::Predicate;
 using std::map;
 using std::set;
@@ -94,6 +95,22 @@
     return config;
 }
 
+StatsdConfig buildGoodRestrictedConfig() {
+    StatsdConfig config;
+    config.set_id(12345);
+    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*/);
+
+    EventMetric* metric = config.add_event_metric();
+    metric->set_id(3);
+    metric->set_what(StringToId("SCREEN_IS_ON"));
+    return config;
+}
+
 set<int32_t> unionSet(const vector<set<int32_t>> sets) {
     set<int32_t> toRet;
     for (const set<int32_t>& s : sets) {
@@ -265,6 +282,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();
@@ -317,6 +371,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());
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/SocketListener_test.cpp b/statsd/tests/SocketListener_test.cpp
new file mode 100644
index 0000000..42c084b
--- /dev/null
+++ b/statsd/tests/SocketListener_test.cpp
@@ -0,0 +1,301 @@
+/*
+ * 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 "socket/StatsSocketListener.h"
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+namespace {
+
+constexpr uint32_t kTestUid = 1001;
+constexpr uint32_t kTestPid = 1002;
+constexpr int kEventCount = 1000;
+constexpr int kEventFilteredCount = 500;
+constexpr int kAtomId = 1000;
+
+class AStatsEventWrapper final {
+    AStatsEvent* statsEvent = nullptr;
+
+public:
+    AStatsEventWrapper(int atomId) {
+        statsEvent = AStatsEvent_obtain();
+        createStatsEvent(statsEvent, INT64_TYPE, /*atomId=*/atomId);
+        AStatsEvent_build(statsEvent);
+    }
+
+    std::pair<const uint8_t*, size_t> getBuffer() const {
+        size_t size;
+        const uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+        return std::make_pair(buf, size);
+    }
+
+    ~AStatsEventWrapper() {
+        AStatsEvent_release(statsEvent);
+    }
+};
+
+}  //  namespace
+
+void generateAtomLogging(const std::shared_ptr<LogEventQueue>& queue,
+                         const std::shared_ptr<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);
+    }
+}
+
+class SocketParseMessageTestNoFiltering : public testing::TestWithParam<bool> {
+protected:
+    std::shared_ptr<LogEventQueue> mEventQueue;
+    std::shared_ptr<LogEventFilter> mLogEventFilter;
+
+public:
+    SocketParseMessageTestNoFiltering()
+        : mEventQueue(std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/)),
+          mLogEventFilter(GetParam() ? std::make_shared<LogEventFilter>() : nullptr) {
+    }
+
+    static std::string ToString(testing::TestParamInfo<bool> info) {
+        return info.param ? "WithEventFilter" : "NoEventFilter";
+    }
+};
+
+INSTANTIATE_TEST_SUITE_P(SocketParseMessageTestNoFiltering, SocketParseMessageTestNoFiltering,
+                         testing::Bool(), SocketParseMessageTestNoFiltering::ToString);
+
+TEST_P(SocketParseMessageTestNoFiltering, TestProcessMessageNoFiltering) {
+    if (GetParam()) {
+        mLogEventFilter->setFilteringEnabled(false);
+    }
+
+    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_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>();
+
+    LogEventFilter::AtomIdSet idsList;
+    logEventFilter->setAtomIds(idsList, nullptr);
+
+    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, TestProcessMessageFilterCompleteSet) {
+    std::shared_ptr<LogEventQueue> eventQueue =
+            std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
+
+    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+
+    LogEventFilter::AtomIdSet idsList;
+    for (int i = 0; i < kEventCount; i++) {
+        idsList.insert(kAtomId + i);
+    }
+    logEventFilter->setAtomIds(idsList, nullptr);
+
+    generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
+
+    // check content of the queue
+    EXPECT_EQ(kEventCount, eventQueue->mQueue.size());
+    for (int i = 0; i < kEventCount; i++) {
+        auto logEvent = eventQueue->waitPop();
+        EXPECT_TRUE(logEvent->isValid());
+        EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+        EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+    }
+}
+
+TEST(SocketParseMessageTest, TestProcessMessageFilterPartialSet) {
+    std::shared_ptr<LogEventQueue> eventQueue =
+            std::make_shared<LogEventQueue>(kEventCount /*buffer limit*/);
+
+    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<LogEventFilter>();
+
+    LogEventFilter::AtomIdSet idsList;
+    for (int i = 0; i < kEventFilteredCount; i++) {
+        idsList.insert(kAtomId + i);
+    }
+    logEventFilter->setAtomIds(idsList, nullptr);
+
+    generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
+
+    // check content of the queue
+    EXPECT_EQ(kEventCount, eventQueue->mQueue.size());
+    for (int i = 0; i < kEventFilteredCount; i++) {
+        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();
+        EXPECT_TRUE(logEvent->isValid());
+        EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+        EXPECT_TRUE(logEvent->isParsedHeaderOnly());
+    }
+}
+
+TEST(SocketParseMessageTest, TestProcessMessageFilterToggle) {
+    std::shared_ptr<LogEventQueue> eventQueue =
+            std::make_shared<LogEventQueue>(kEventCount * 3 /*buffer limit*/);
+
+    std::shared_ptr<LogEventFilter> logEventFilter = std::make_shared<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);
+
+    generateAtomLogging(eventQueue, logEventFilter, kEventCount, kAtomId);
+
+    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::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);
+
+    // 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());
+    // events with ids from kAtomId to kAtomId + kEventFilteredCount should not be skipped
+    for (int i = 0; i < kEventFilteredCount; i++) {
+        auto logEvent = eventQueue->waitPop();
+        EXPECT_TRUE(logEvent->isValid());
+        EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+        EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+    }
+
+    // all events above kAtomId + kEventFilteredCount to kAtomId + kEventCount should be skipped
+    for (int i = kEventFilteredCount; i < kEventCount; i++) {
+        auto logEvent = eventQueue->waitPop();
+        EXPECT_TRUE(logEvent->isValid());
+        EXPECT_EQ(kAtomId + i, logEvent->GetTagId());
+        EXPECT_TRUE(logEvent->isParsedHeaderOnly());
+    }
+
+    // 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();
+        EXPECT_TRUE(logEvent->isValid());
+        EXPECT_EQ(kAtomId + kEventCount + i, logEvent->GetTagId());
+        EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+    }
+
+    // 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();
+        EXPECT_TRUE(logEvent->isValid());
+        EXPECT_EQ(kAtomId + kEventCount * 2 + i, logEvent->GetTagId());
+        EXPECT_TRUE(logEvent->isParsedHeaderOnly());
+    }
+
+    // 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();
+        EXPECT_TRUE(logEvent->isValid());
+        EXPECT_EQ(kAtomId + kEventCount * 2 + i, logEvent->GetTagId());
+        EXPECT_FALSE(logEvent->isParsedHeaderOnly());
+    }
+}
+
+// TODO: tests for setAtomIds() with multiple consumers
+// TODO: use MockLogEventFilter to test different sets from different consumers
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/statsd/tests/StatsLogProcessor_test.cpp b/statsd/tests/StatsLogProcessor_test.cpp
index 019584e..d29b5e6 100644
--- a/statsd/tests/StatsLogProcessor_test.cpp
+++ b/statsd/tests/StatsLogProcessor_test.cpp
@@ -15,6 +15,7 @@
 #include "StatsLogProcessor.h"
 
 #include <android-base/stringprintf.h>
+#include <android-modules-utils/sdk_level.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
@@ -26,9 +27,11 @@
 #include "packages/UidMap.h"
 #include "src/stats_log.pb.h"
 #include "src/statsd_config.pb.h"
+#include "state/StateManager.h"
 #include "statslog_statsdtest.h"
 #include "storage/StorageManager.h"
 #include "tests/statsd_test_util.h"
+#include "utils/DbUtils.h"
 
 using namespace android;
 using namespace testing;
@@ -40,8 +43,11 @@
 namespace statsd {
 
 using android::base::StringPrintf;
+using android::modules::sdklevel::IsAtLeastU;
 using android::util::ProtoOutputStream;
 
+using ::testing::Expectation;
+
 #ifdef __ANDROID__
 #define STATS_DATA_DIR "/data/misc/stats-data"
 
@@ -50,20 +56,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) {
@@ -72,9 +87,11 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> periodicAlarmMonitor;
     // Construct the processor with a no-op sendBroadcast function that does nothing.
-    StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
-                        [](const ConfigKey& key) { return true; },
-                        [](const int&, const vector<int64_t>&) {return true;});
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
+            [](const ConfigKey& key) { return true; },
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, nullptr);
 
     MockMetricsManager mockMetricsManager;
 
@@ -93,12 +110,14 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
-    StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [&broadcastCount](const ConfigKey& key) {
-                            broadcastCount++;
-                            return true;
-                        },
-                        [](const int&, const vector<int64_t>&) {return true;});
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+            [&broadcastCount](const ConfigKey& key) {
+                broadcastCount++;
+                return true;
+            },
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, nullptr);
 
     MockMetricsManager mockMetricsManager;
 
@@ -125,12 +144,14 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
-    StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [&broadcastCount](const ConfigKey& key) {
-                            broadcastCount++;
-                            return true;
-                        },
-                        [](const int&, const vector<int64_t>&) {return true;});
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+            [&broadcastCount](const ConfigKey& key) {
+                broadcastCount++;
+                return true;
+            },
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, nullptr);
 
     MockMetricsManager mockMetricsManager;
 
@@ -161,8 +182,49 @@
     return config;
 }
 
+StatsdConfig makeRestrictedConfig(bool includeMetric = false) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");
+    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) {
-    // Setup simple config key corresponding to empty config.
+    ConfigKey key(3, 4);
+    StatsdConfig config = MakeConfig(true);
+
     sp<UidMap> m = new UidMap();
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
     m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
@@ -171,14 +233,21 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
-    StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [&broadcastCount](const ConfigKey& key) {
-                            broadcastCount++;
-                            return true;
-                        },
-                        [](const int&, const vector<int64_t>&) {return true;});
-    ConfigKey key(3, 4);
-    StatsdConfig config = MakeConfig(true);
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+            [&broadcastCount](const ConfigKey& key) {
+                broadcastCount++;
+                return true;
+            },
+            [](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);
+
     p.OnConfigUpdated(0, key, config);
 
     // Expect to get no metrics, but snapshot specified above in uidmap.
@@ -195,6 +264,9 @@
 
 TEST(StatsLogProcessorTest, TestEmptyConfigHasNoUidMap) {
     // Setup simple config key corresponding to empty config.
+    ConfigKey key(3, 4);
+    StatsdConfig config = MakeConfig(false);
+
     sp<UidMap> m = new UidMap();
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
     m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
@@ -203,14 +275,21 @@
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
-    StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [&broadcastCount](const ConfigKey& key) {
-                            broadcastCount++;
-                            return true;
-                        },
-                        [](const int&, const vector<int64_t>&) {return true;});
-    ConfigKey key(3, 4);
-    StatsdConfig config = MakeConfig(false);
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+            [&broadcastCount](const ConfigKey& key) {
+                broadcastCount++;
+                return true;
+            },
+            [](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);
+
     p.OnConfigUpdated(0, key, config);
 
     // Expect to get no metrics, but snapshot specified above in uidmap.
@@ -225,23 +304,33 @@
 
 TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) {
     // Setup simple config key corresponding to empty config.
-    sp<UidMap> m = new UidMap();
-    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
-    sp<AlarmMonitor> anomalyAlarmMonitor;
-    sp<AlarmMonitor> subscriberAlarmMonitor;
-    int broadcastCount = 0;
-    StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [&broadcastCount](const ConfigKey& key) {
-                            broadcastCount++;
-                            return true;
-                        },
-                        [](const int&, const vector<int64_t>&) {return true;});
     ConfigKey key(3, 4);
     StatsdConfig config;
     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();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> subscriberAlarmMonitor;
+    int broadcastCount = 0;
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+            [&broadcastCount](const ConfigKey& key) {
+                broadcastCount++;
+                return true;
+            },
+            [](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);
+
     p.OnConfigUpdated(1, key, config);
 
     // Expect to get no metrics, but snapshot specified above in uidmap.
@@ -307,16 +396,25 @@
 
 TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate) {
     // Setup simple config key corresponding to empty config.
+    ConfigKey key(3, 4);
+    StatsdConfig config = MakeConfig(false);
+
     sp<UidMap> m = new UidMap();
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
     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; });
-    ConfigKey key(3, 4);
-    StatsdConfig config = MakeConfig(false);
+            [](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);
+
     p.OnConfigUpdated(0, key, config);
     EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
 
@@ -329,7 +427,9 @@
 }
 
 TEST(StatsLogProcessorTest, InvalidConfigRemoved) {
-    // Setup simple config key corresponding to empty config.
+    ConfigKey key(3, 4);
+    StatsdConfig config = MakeConfig(true);
+
     sp<UidMap> m = new UidMap();
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
     m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
@@ -337,11 +437,22 @@
                  /* certificateHash */ {{}, {}});
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> subscriberAlarmMonitor;
-    StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [](const ConfigKey& key) { return true; },
-                        [](const int&, const vector<int64_t>&) {return true;});
-    ConfigKey key(3, 4);
-    StatsdConfig config = MakeConfig(true);
+    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);
+
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(CreateAtomIdSetDefault(), &p)).Times(1);
+    // atom used by matcher defined in MakeConfig() API
+    Expectation exp =
+            EXPECT_CALL(*mockLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), &p))
+                    .Times(1);
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(CreateAtomIdSetDefault(), &p)).Times(1).After(exp);
+
     // Remove the config mConfigStats so that the Icebox starts at 0 configs.
     p.OnConfigRemoved(key);
     StatsdStats::getInstance().reset();
@@ -463,6 +574,9 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
     StatsLogProcessor processor(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, timeBase1,
             [](const ConfigKey& key) { return true; },
@@ -474,7 +588,12 @@
                 activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
                                               activeConfigs.end());
                 return true;
-            });
+            },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+    // config1,config2,config3 use the same atom
+    const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config1);
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(3);
 
     processor.OnConfigUpdated(1, cfgKey1, config1);
     processor.OnConfigUpdated(2, cfgKey2, config2);
@@ -1561,7 +1680,7 @@
     // Send the config.
     const sp<UidMap> uidMap = new UidMap();
     const shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr, nullptr);
     string serialized = config1.SerializeAsString();
     service->addConfigurationChecked(uid, configId, {serialized.begin(), serialized.end()});
 
@@ -1740,6 +1859,29 @@
                                       ADB_DUMP, NO_TIME_CONSTRAINTS, nullptr);
 }
 
+TEST(StatsLogProcessorTest, LogEventFilterOnSetPrintLogs) {
+    sp<UidMap> m = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> periodicAlarmMonitor;
+
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
+            [](const ConfigKey& key) { return true; },
+            [](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);
+    EXPECT_CALL(*mockLogEventFilter, setFilteringEnabled(true)).Times(1).After(filterSetFalse);
+
+    p.setPrintLogs(true);
+    p.setPrintLogs(false);
+}
+
 TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogHostUid) {
     int hostUid = 20;
     int isolatedUid = 30;
@@ -2006,6 +2148,201 @@
     EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns);
 }
 
+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();
+    m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")},
+                 {String16("p1"), String16("p2")}, {String16(""), String16("")},
+                 /* certificateHash */ {{}, {}});
+    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 bd76081..ed964a3 100644
--- a/statsd/tests/StatsService_test.cpp
+++ b/statsd/tests/StatsService_test.cpp
@@ -31,15 +31,45 @@
 namespace os {
 namespace statsd {
 
+using android::modules::sdklevel::IsAtLeastU;
 using android::util::ProtoOutputStream;
 using ::ndk::SharedRefBase;
 
 #ifdef __ANDROID__
 
+namespace {
+
+const int64_t metricId = 123456;
+const int32_t ATOM_TAG = util::SUBSYSTEM_SLEEP_STATE;
+
+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;
+    *config.add_gauge_metric() =
+            createGaugeMetric("GAUGE1", atomMatcher.id(), samplingType, nullopt, nullopt);
+    config.set_hash_strings_in_metric_report(false);
+    return config;
+}
+
+class FakeSubsystemSleepCallbackWithTiming : public FakeSubsystemSleepCallback {
+public:
+    Status onPullAtom(int atomTag,
+                      const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override {
+        mPullTimeNs = getElapsedRealtimeNs();
+        return FakeSubsystemSleepCallback::onPullAtom(atomTag, resultReceiver);
+    }
+    int64_t mPullTimeNs = 0;
+};
+
+}  // namespace
+
 TEST(StatsServiceTest, TestAddConfig_simple) {
     const sp<UidMap> uidMap = new UidMap();
-    shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
     const int kConfigKey = 12345;
     const int kCallingUid = 123;
     StatsdConfig config;
@@ -57,8 +87,8 @@
 
 TEST(StatsServiceTest, TestAddConfig_empty) {
     const sp<UidMap> uidMap = new UidMap();
-    shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
     string serialized = "";
     const int kConfigKey = 12345;
     const int kCallingUid = 123;
@@ -73,8 +103,8 @@
 
 TEST(StatsServiceTest, TestAddConfig_invalid) {
     const sp<UidMap> uidMap = new UidMap();
-    shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
     string serialized = "Invalid config!";
 
     EXPECT_FALSE(
@@ -92,8 +122,8 @@
     int32_t uid;
 
     const sp<UidMap> uidMap = new UidMap();
-    shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
     service->mEngBuild = true;
 
     // "-1"
@@ -118,6 +148,114 @@
     EXPECT_FALSE(service->getUidFromArgs(args, 2, uid));
 }
 
+class StatsServiceStatsdInitTest : public StatsServiceConfigTest,
+                                   public testing::WithParamInterface<bool> {
+public:
+    StatsServiceStatsdInitTest() : kInitDelaySec(GetParam() ? 0 : 3) {
+    }
+
+    static std::string ToString(testing::TestParamInfo<bool> info) {
+        return info.param ? "NoDelay" : "WithDelay";
+    }
+
+protected:
+    const int kInitDelaySec = 0;
+
+    shared_ptr<StatsService> createStatsService() override {
+        return SharedRefBase::make<StatsService>(new UidMap(), /*queue=*/nullptr,
+                                                 /*LogEventFilter=*/nullptr,
+                                                 /*initEventDelaySecs=*/kInitDelaySec);
+    }
+};
+
+INSTANTIATE_TEST_SUITE_P(StatsServiceStatsdInitTest, StatsServiceStatsdInitTest, testing::Bool(),
+                         StatsServiceStatsdInitTest::ToString);
+
+TEST_P(StatsServiceStatsdInitTest, StatsServiceStatsdInitTest) {
+    // used for error threshold tolerance due to sleep() is involved
+    const int64_t ERROR_THRESHOLD_NS = GetParam() ? 1000000 : 5 * 1000000;
+
+    auto pullAtomCallback = SharedRefBase::make<FakeSubsystemSleepCallbackWithTiming>();
+
+    // TODO: evaluate to use service->registerNativePullAtomCallback() API
+    service->mPullerManager->RegisterPullAtomCallback(/*uid=*/0, ATOM_TAG, NS_PER_SEC,
+                                                      NS_PER_SEC * 10, {}, pullAtomCallback);
+
+    const int64_t createConfigTimeNs = getElapsedRealtimeNs();
+    StatsdConfig config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE);
+    config.set_id(kConfigKey);
+    ASSERT_TRUE(sendConfig(config));
+    ASSERT_EQ(2, pullAtomCallback->pullNum);
+
+    service->mProcessor->mPullerManager->ForceClearPullerCache();
+
+    const int64_t initCompletedTimeNs = getElapsedRealtimeNs();
+    service->onStatsdInitCompleted();
+    ASSERT_EQ(3, pullAtomCallback->pullNum);
+
+    // Checking pull with or without delay according to the flag value
+    const int64_t lastPullNs = pullAtomCallback->mPullTimeNs;
+
+    if (GetParam()) {
+        // when flag is defined - should be small delay between init & pull
+        // expect delay smaller than 1 second
+        EXPECT_GE(lastPullNs, initCompletedTimeNs);
+        EXPECT_LE(lastPullNs, initCompletedTimeNs + ERROR_THRESHOLD_NS);
+    } else {
+        // when flag is not defined - big delay is expected (kInitDelaySec)
+        EXPECT_GE(lastPullNs, initCompletedTimeNs + kInitDelaySec * NS_PER_SEC);
+        EXPECT_LE(lastPullNs,
+                  initCompletedTimeNs + kInitDelaySec * NS_PER_SEC + ERROR_THRESHOLD_NS);
+    }
+
+    const int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+    const int64_t dumpReportTsNanos = createConfigTimeNs + bucketSizeNs + NS_PER_SEC;
+
+    vector<uint8_t> output;
+    ConfigKey configKey(kCallingUid, kConfigKey);
+    service->mProcessor->onDumpReport(configKey, dumpReportTsNanos,
+                                      /*include_current_bucket=*/false, /*erase_data=*/true,
+                                      ADB_DUMP, FAST, &output);
+    ConfigMetricsReportList reports;
+    reports.ParseFromArray(output.data(), output.size());
+    ASSERT_EQ(1, reports.reports_size());
+
+    backfillDimensionPath(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+    StatsLogReport::GaugeMetricDataWrapper gaugeMetrics =
+            reports.reports(0).metrics(0).gauge_metrics();
+    ASSERT_EQ(gaugeMetrics.skipped_size(), 0);
+    ASSERT_GT((int)gaugeMetrics.data_size(), 0);
+    const auto data = gaugeMetrics.data(0);
+    ASSERT_EQ(2, data.bucket_info_size());
+
+    const auto bucketInfo0 = data.bucket_info(0);
+    const auto bucketInfo1 = data.bucket_info(1);
+
+    EXPECT_GE(NanoToMillis(bucketInfo0.start_bucket_elapsed_nanos()),
+              NanoToMillis(createConfigTimeNs));
+    EXPECT_LE(NanoToMillis(bucketInfo0.start_bucket_elapsed_nanos()),
+              NanoToMillis(createConfigTimeNs + ERROR_THRESHOLD_NS));
+
+    EXPECT_EQ(NanoToMillis(bucketInfo0.end_bucket_elapsed_nanos()),
+              NanoToMillis(bucketInfo1.start_bucket_elapsed_nanos()));
+
+    ASSERT_EQ(1, bucketInfo1.atom_size());
+    ASSERT_GT(bucketInfo1.atom(0).subsystem_sleep_state().time_millis(), 0);
+
+    EXPECT_GE(NanoToMillis(bucketInfo1.start_bucket_elapsed_nanos()),
+              NanoToMillis(createConfigTimeNs + kInitDelaySec * NS_PER_SEC));
+    EXPECT_LE(NanoToMillis(bucketInfo1.start_bucket_elapsed_nanos()),
+              NanoToMillis(createConfigTimeNs + kInitDelaySec * NS_PER_SEC + ERROR_THRESHOLD_NS));
+
+    EXPECT_GE(NanoToMillis(createConfigTimeNs + bucketSizeNs),
+              NanoToMillis(bucketInfo1.end_bucket_elapsed_nanos()));
+    EXPECT_LE(NanoToMillis(createConfigTimeNs + bucketSizeNs),
+              NanoToMillis(bucketInfo1.end_bucket_elapsed_nanos() + ERROR_THRESHOLD_NS));
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/UidMap_test.cpp b/statsd/tests/UidMap_test.cpp
index a303ac6..6d3dd7e 100644
--- a/statsd/tests/UidMap_test.cpp
+++ b/statsd/tests/UidMap_test.cpp
@@ -22,6 +22,7 @@
 #include "StatsLogProcessor.h"
 #include "StatsService.h"
 #include "config/ConfigKey.h"
+#include "gtest_matchers.h"
 #include "guardrail/StatsdStats.h"
 #include "hash.h"
 #include "logd/LogEvent.h"
@@ -53,13 +54,6 @@
 const vector<vector<uint8_t>> kCertificateHashes{{'a', 'z'}, {'b', 'c'}, {'d', 'e'}};
 const vector<bool> kDeleted(3, false);
 
-void sendIncludeCertificateHashFlagToStatsd(shared_ptr<StatsService> service, bool flag) {
-    PropertyParcel certHashParcel;
-    certHashParcel.property = kIncludeCertificateHash;
-    certHashParcel.value = flag ? "true" : "false";
-    service->updateProperties({certHashParcel});
-}
-
 void sendPackagesToStatsd(shared_ptr<StatsService> service, const vector<int32_t>& uids,
                           const vector<int64_t>& versions, const vector<string>& versionStrings,
                           const vector<string>& apps, const vector<string>& installers,
@@ -114,7 +108,8 @@
     StatsLogProcessor p(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
             [](const ConfigKey& key) { return true; },
-            [](const int&, const vector<int64_t>&) { return true; });
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, nullptr);
 
     std::unique_ptr<LogEvent> addEvent = CreateIsolatedUidChangedEvent(
             1 /*timestamp*/, 100 /*hostUid*/, 101 /*isolatedUid*/, 1 /*is_create*/);
@@ -130,8 +125,8 @@
 
 TEST(UidMapTest, TestUpdateMap) {
     const sp<UidMap> uidMap = new UidMap();
-    const shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+    const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
     sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
                          kCertificateHashes);
 
@@ -149,22 +144,21 @@
     name_set = uidMap->getAppNamesFromUid(12345, true /* returnNormalized */);
     EXPECT_THAT(name_set, IsEmpty());
 
-    // Certificate hashes are not sent to statsd by default (when flag is not present).
     vector<PackageInfo> expectedPackageInfos =
             buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
-                              /* certHashes */ {}, kDeleted, /* installerIndices */ {},
+                              kCertificateHashes, kDeleted, /* installerIndices */ {},
                               /* hashStrings */ false);
 
     PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
 
     EXPECT_THAT(packageInfoSnapshot.package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+                UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
 }
 
 TEST(UidMapTest, TestUpdateMapMultiple) {
     const sp<UidMap> uidMap = new UidMap();
-    const shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+    const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
     sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
                          kCertificateHashes);
 
@@ -194,22 +188,21 @@
     name_set = uidMap->getAppNamesFromUid(1500, true /* returnNormalized */);
     EXPECT_THAT(name_set, IsEmpty());
 
-    // Certificate hashes are not sent to statsd by default (when flag is not present).
     vector<PackageInfo> expectedPackageInfos =
             buildPackageInfos(apps, uids, kVersions, kVersionStrings, installers,
-                              /* certHashes */ {}, kDeleted, /* installerIndices */ {},
+                              kCertificateHashes, kDeleted, /* installerIndices */ {},
                               /* hashStrings */ false);
 
     PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
 
     EXPECT_THAT(packageInfoSnapshot.package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+                UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
 }
 
 TEST(UidMapTest, TestRemoveApp) {
     const sp<UidMap> uidMap = new UidMap();
-    const shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+    const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
     sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
                          kCertificateHashes);
 
@@ -224,11 +217,11 @@
     deleted[0] = true;
     vector<PackageInfo> expectedPackageInfos =
             buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
-                              /* certHashes */ {}, deleted, /* installerIndices */ {},
+                              kCertificateHashes, deleted, /* installerIndices */ {},
                               /* hashStrings */ false);
     PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
     EXPECT_THAT(packageInfoSnapshot.package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+                UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
 
     service->informOnePackageRemoved(kApp2, 1000);
     EXPECT_FALSE(uidMap->hasApp(1000, kApp1));
@@ -239,13 +232,12 @@
     EXPECT_THAT(name_set, IsEmpty());
 
     deleted[1] = true;
-    expectedPackageInfos =
-            buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
-                              /* certHashes */ {}, deleted, /* installerIndices */ {},
-                              /* hashStrings */ false);
+    expectedPackageInfos = buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
+                                             kCertificateHashes, deleted, /* installerIndices */ {},
+                                             /* hashStrings */ false);
     packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
     EXPECT_THAT(packageInfoSnapshot.package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+                UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
 
     service->informOnePackageRemoved(kApp3, 1500);
     EXPECT_FALSE(uidMap->hasApp(1000, kApp1));
@@ -256,19 +248,18 @@
     EXPECT_THAT(name_set, IsEmpty());
 
     deleted[2] = true;
-    expectedPackageInfos =
-            buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
-                              /* certHashes */ {}, deleted, /* installerIndices */ {},
-                              /* hashStrings */ false);
+    expectedPackageInfos = buildPackageInfos(kApps, kUids, kVersions, kVersionStrings, kInstallers,
+                                             kCertificateHashes, deleted, /* installerIndices */ {},
+                                             /* hashStrings */ false);
     packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
     EXPECT_THAT(packageInfoSnapshot.package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+                UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
 }
 
 TEST(UidMapTest, TestUpdateApp) {
     const sp<UidMap> uidMap = new UidMap();
-    const shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+    const shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(
+            uidMap, /* queue */ nullptr, /* LogEventFilter */ nullptr);
     sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
                          kCertificateHashes);
 
@@ -308,15 +299,15 @@
     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});
-    // Certificate hashes are not sent to statsd by default (when flag is not present).
+    vector<vector<uint8_t>> certHashes = concatenate(kCertificateHashes, {{'a'}, {'b'}});
     vector<PackageInfo> expectedPackageInfos =
-            buildPackageInfos(apps, uids, versions, versionStrings, installers, /* certHashes */ {},
-                              deleted, /* installerIndices */ {},
+            buildPackageInfos(apps, uids, versions, versionStrings, installers, certHashes, deleted,
+                              /* installerIndices */ {},
                               /* hashStrings */ false);
 
     PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
     EXPECT_THAT(packageInfoSnapshot.package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+                UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
 }
 
 // Test that uid map returns at least one snapshot even if we already obtained
@@ -568,81 +559,6 @@
     ASSERT_EQ(1U, m.mChanges.size());
 }
 
-// Set up parameterized test for testing with different boolean flag values that gate the inclusion
-// of package certificate hashes in UidMap.
-class UidMapTestWithAppCertificateFlag : public TestWithParam<bool> {
-protected:
-    const sp<UidMap> uidMap;
-    const shared_ptr<StatsService> service;
-    vector<PackageInfo> expectedPackageInfos;
-
-    UidMapTestWithAppCertificateFlag()
-        : uidMap(new UidMap()),
-          service(SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr)) {
-    }
-
-    void SetUp() override {
-        sendIncludeCertificateHashFlagToStatsd(service, GetParam());
-        sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
-                             kCertificateHashes);
-
-        vector<vector<uint8_t>> emptyCertHashes;
-        vector<vector<uint8_t>> certHashes = GetParam() ? kCertificateHashes : emptyCertHashes;
-        expectedPackageInfos = buildPackageInfos(
-                kApps, kUids, kVersions, kVersionStrings, kInstallers, certHashes, kDeleted,
-                /* installerIndices */ {}, /* hashStrings */ false);
-    }
-};
-
-INSTANTIATE_TEST_SUITE_P(Boolean, UidMapTestWithAppCertificateFlag, Bool(),
-                         PrintToStringParamName());
-
-TEST_P(UidMapTestWithAppCertificateFlag, TestUpdateMap) {
-    PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
-
-    EXPECT_THAT(packageInfoSnapshot.package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
-}
-
-TEST_P(UidMapTestWithAppCertificateFlag, TestUpdateApp) {
-    // Add a new name for uid 1000.
-    service->informOnePackage("NeW_aPP1_NAmE", 1000, /* version */ 40,
-                              /* versionString */ "v40", /* installer */ "com.android.vending",
-                              /* certificateHash */ {'a'});
-
-    vector<uint8_t> certHash;
-    if (GetParam()) {
-        certHash = {'a'};
-    }
-    PackageInfo expectedPackageInfo =
-            buildPackageInfo("NeW_aPP1_NAmE", 1000, /* version */ 40, /* versionString */ "v40",
-                             /* installer */ "com.android.vending", certHash, /* deleted */ false,
-                             /* hashStrings */ false, /* installerIndex */ nullopt);
-    expectedPackageInfos.push_back(expectedPackageInfo);
-
-    PackageInfoSnapshot packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
-    EXPECT_THAT(packageInfoSnapshot.package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
-
-    // Update installer and certificate for the same package.
-    service->informOnePackage("NeW_aPP1_NAmE", 1000, /* version */ 40,
-                              /* versionString */ "v40", /* installer */ "new_installer",
-                              /* certificateHash */ {'n', 'e', 'w'});
-
-    if (GetParam()) {
-        certHash = {'n', 'e', 'w'};
-    }
-    expectedPackageInfo =
-            buildPackageInfo("NeW_aPP1_NAmE", 1000, /* version */ 40, /* versionString */ "v40",
-                             /* installer */ "new_installer", certHash, /* deleted */ false,
-                             /* hashStrings */ false, /* installerIndex */ nullopt);
-    expectedPackageInfos.back() = expectedPackageInfo;
-
-    packageInfoSnapshot = getPackageInfoSnapshot(uidMap);
-    EXPECT_THAT(packageInfoSnapshot.package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
-}
-
 class UidMapTestAppendUidMap : public Test {
 protected:
     const ConfigKey config1;
@@ -656,11 +572,11 @@
     UidMapTestAppendUidMap()
         : config1(1, StringToId("config1")),
           uidMap(new UidMap()),
-          service(SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr)) {
+          service(SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr,
+                                                    /* LogEventFilter */ nullptr)) {
     }
 
     void SetUp() override {
-        sendIncludeCertificateHashFlagToStatsd(service, true);
         sendPackagesToStatsd(service, kUids, kVersions, kVersionStrings, kApps, kInstallers,
                              kCertificateHashes);
 
@@ -707,7 +623,7 @@
     EXPECT_THAT(strSet, IsSupersetOf(kApps));
 
     EXPECT_THAT(results.snapshots(0).package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+                UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
 }
 
 TEST_F(UidMapTestAppendUidMap, TestInstallersInReportIncludeInstallerAndDontHashStrings) {
@@ -735,7 +651,7 @@
                               /* hashStrings */ false);
 
     EXPECT_THAT(results.snapshots(0).package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+                UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
 }
 
 // Set up parameterized test with set<string>* parameter to control whether strings are hashed
@@ -813,7 +729,7 @@
                               /* hashStrings */ false);
 
     EXPECT_THAT(results.snapshots(0).package_info(),
-                UnorderedPointwise(ProtoEq(), expectedPackageInfos));
+                UnorderedPointwise(EqPackageInfo(), expectedPackageInfos));
 }
 
 #else
diff --git a/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp b/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp
index 92955bb..e078f47 100644
--- a/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp
+++ b/statsd/tests/e2e/ConfigUpdate_e2e_ab_test.cpp
@@ -98,7 +98,8 @@
     ASSERT_EQ(uidMapping.snapshots_size(), 1);
     ASSERT_EQ(uidMapping.snapshots(0).package_info_size(), 1);
     EXPECT_FALSE(uidMapping.snapshots(0).package_info(0).has_version_string());
-    EXPECT_EQ(uidMapping.snapshots(0).package_info(0).installer(), "installer1");
+    EXPECT_EQ(uidMapping.snapshots(0).package_info(0).installer_index(), 0);
+    EXPECT_THAT(uidMapping.installer_name(), ElementsAre("installer1"));
 }
 
 TEST_P(ConfigUpdateE2eAbTest, TestHashStrings) {
diff --git a/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp b/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
index 2250da3..ead1c46 100644
--- a/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
+++ b/statsd/tests/e2e/ConfigUpdate_e2e_test.cpp
@@ -47,12 +47,44 @@
     EXPECT_EQ(value.value_tuple().dimensions_value(0).value_str(), name);
 }
 
+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);
+    }
+
+    return CreateStatsLogProcessor(timeBaseNs, currentTimeNs, config, key, nullptr, 0, new UidMap(),
+                                   logEventFilter);
+}
+
 }  // Anonymous namespace.
 
 // Setup for test fixture.
-class ConfigUpdateE2eTest : public ::testing::Test {};
+class ConfigUpdateE2eTest : public testing::TestWithParam<bool> {
+protected:
+    std::shared_ptr<MockLogEventFilter> mLogEventFilter;
 
-TEST_F(ConfigUpdateE2eTest, TestEventMetric) {
+    void SetUp() override {
+        mLogEventFilter = GetParam() ? std::make_shared<MockLogEventFilter>() : nullptr;
+    }
+
+public:
+    static std::string ToString(testing::TestParamInfo<bool> info) {
+        return info.param ? "WithLogEventFilter" : "NoLogEventFilter";
+    }
+};
+
+INSTANTIATE_TEST_SUITE_P(ConfigUpdateE2eTest, ConfigUpdateE2eTest, testing::Bool(),
+                         ConfigUpdateE2eTest::ToString);
+
+TEST_P(ConfigUpdateE2eTest, TestEventMetric) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
 
@@ -99,8 +131,8 @@
 
     ConfigKey key(123, 987);
     uint64_t bucketStartTimeNs = 10000000000;  // 0:10
-    sp<StatsLogProcessor> processor =
-            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+                                                              config, key, mLogEventFilter);
 
     int app1Uid = 123;
     vector<int> attributionUids1 = {app1Uid};
@@ -161,6 +193,11 @@
     *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);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Send events after the update.
@@ -274,7 +311,7 @@
     EXPECT_EQ(data.atom().sync_state_changed().sync_name(), "sync3");
 }
 
-TEST_F(ConfigUpdateE2eTest, TestCountMetric) {
+TEST_P(ConfigUpdateE2eTest, TestCountMetric) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
 
@@ -336,8 +373,8 @@
     ConfigKey key(123, 987);
     uint64_t bucketStartTimeNs = 10000000000;  // 0:10
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
-    sp<StatsLogProcessor> processor =
-            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+                                                              config, key, mLogEventFilter);
 
     int app1Uid = 123, app2Uid = 456;
     vector<int> attributionUids1 = {app1Uid};
@@ -399,6 +436,11 @@
     *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);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Send events after the update. Counts reset to 0 since this is a new bucket.
@@ -527,7 +569,7 @@
     ValidateCountBucket(data.bucket_info(0), updateTimeNs, bucketStartTimeNs + bucketSizeNs, 2);
 }
 
-TEST_F(ConfigUpdateE2eTest, TestDurationMetric) {
+TEST_P(ConfigUpdateE2eTest, TestDurationMetric) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
 
@@ -607,8 +649,8 @@
     ConfigKey key(123, 987);
     uint64_t bucketStartTimeNs = 10000000000;  // 0:10
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
-    sp<StatsLogProcessor> processor =
-            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+                                                              config, key, mLogEventFilter);
 
     int app1Uid = 123, app2Uid = 456, app3Uid = 789;
     vector<int> attributionUids1 = {app1Uid};
@@ -683,6 +725,11 @@
     // 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);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Send events after the update.
@@ -880,7 +927,7 @@
     ValidateDurationBucket(data.bucket_info(0), updateTimeNs, bucketEndTimeNs, 20 * NS_PER_SEC);
 }
 
-TEST_F(ConfigUpdateE2eTest, TestGaugeMetric) {
+TEST_P(ConfigUpdateE2eTest, TestGaugeMetric) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
@@ -941,9 +988,19 @@
     ConfigKey key(123, 987);
     uint64_t bucketStartTimeNs = getElapsedRealtimeNs();  // 0:10
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
-    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
-            bucketStartTimeNs, bucketStartTimeNs, config, key,
-            SharedRefBase::make<FakeSubsystemSleepCallback>(), util::SUBSYSTEM_SLEEP_STATE);
+    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);
+    }
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                    util::SUBSYSTEM_SLEEP_STATE, new UidMap(), mLogEventFilter);
 
     int app1Uid = 123, app2Uid = 456;
 
@@ -1009,6 +1066,9 @@
 
     int64_t updateTimeNs = bucketStartTimeNs + 60 * NS_PER_SEC;
     // Update pulls gaugePullPersist and gaugeNew.
+    if (GetParam()) {
+        EXPECT_CALL(*mLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(newConfig), _)).Times(1);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Verify puller manager is properly set.
@@ -1275,7 +1335,7 @@
     EXPECT_EQ(data.bucket_info(0).atom(0).subsystem_sleep_state().time_millis(), 902);
 }
 
-TEST_F(ConfigUpdateE2eTest, TestValueMetric) {
+TEST_P(ConfigUpdateE2eTest, TestValueMetric) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
     config.add_default_pull_packages("AID_ROOT");  // Fake puller is registered with root.
@@ -1329,10 +1389,20 @@
     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);
+    }
     // Config creation triggers pull #1.
-    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
-            bucketStartTimeNs, bucketStartTimeNs, config, key,
-            SharedRefBase::make<FakeSubsystemSleepCallback>(), util::SUBSYSTEM_SLEEP_STATE);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key,
+                                    SharedRefBase::make<FakeSubsystemSleepCallback>(),
+                                    util::SUBSYSTEM_SLEEP_STATE, new UidMap(), mLogEventFilter);
 
     // Initialize log events before update.
     // ValuePushPersist and ValuePullPersist will skip the bucket due to condition unknown.
@@ -1380,6 +1450,9 @@
 
     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);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Verify puller manager is properly set.
@@ -1602,7 +1675,7 @@
     EXPECT_EQ(skipBucket.drop_event(0).drop_reason(), BucketDropReason::DUMP_REPORT_REQUESTED);
 }
 
-TEST_F(ConfigUpdateE2eTest, TestKllMetric) {
+TEST_P(ConfigUpdateE2eTest, TestKllMetric) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
 
@@ -1637,8 +1710,8 @@
     ConfigKey key(123, 987);
     const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
-    sp<StatsLogProcessor> processor =
-            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+                                                              config, key, mLogEventFilter);
 
     // Initialize log events before update.
     // kllPersist and kllRemove will skip the bucket due to condition unknown.
@@ -1684,6 +1757,11 @@
     *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);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Send events after the update. This is a new bucket.
@@ -1815,7 +1893,7 @@
     EXPECT_EQ(kllPersistAfter.kll_metrics().skipped_size(), 0);
 }
 
-TEST_F(ConfigUpdateE2eTest, TestMetricActivation) {
+TEST_P(ConfigUpdateE2eTest, TestMetricActivation) {
     ALOGE("Start ConfigUpdateE2eTest#TestMetricActivation");
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
@@ -1893,8 +1971,8 @@
     ConfigKey key(123, 987);
     uint64_t bucketStartTimeNs = 10000000000;  // 0:10
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
-    sp<StatsLogProcessor> processor =
-            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+                                                              config, key, mLogEventFilter);
     int uid1 = 55555;
 
     // Initialize log events before update.
@@ -1943,6 +2021,11 @@
     *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);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // The reboot will write to disk again, so sleep for 1 second to avoid this.
@@ -1961,7 +2044,7 @@
 
     // On boot, use StartUp. However, skip config manager for simplicity.
     int64_t bootTimeNs = bucketStartTimeNs + 55 * NS_PER_SEC;
-    processor = CreateStatsLogProcessor(bootTimeNs, bootTimeNs, newConfig, key);
+    processor = CreateStatsLogProcessor(bootTimeNs, bootTimeNs, newConfig, key, mLogEventFilter);
     processor->LoadActiveConfigsFromDisk();
     processor->LoadMetadataFromDisk(getWallClockNs(), bootTimeNs);
 
@@ -2066,7 +2149,7 @@
     ALOGE("End ConfigUpdateE2eTest#TestMetricActivation");
 }
 
-TEST_F(ConfigUpdateE2eTest, TestAnomalyCountMetric) {
+TEST_P(ConfigUpdateE2eTest, TestAnomalyCountMetric) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
 
@@ -2163,8 +2246,8 @@
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
     uint64_t bucketStartTimeNs = 10000000000;  // 0:10
     uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
-    sp<StatsLogProcessor> processor =
-            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+                                                              config, key, mLogEventFilter);
 
     StatsDimensionsValueParcel wlUid1 =
             CreateAttributionUidDimensionsValueParcel(util::WAKELOCK_STATE_CHANGED, app1Uid);
@@ -2242,6 +2325,11 @@
     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);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, newConfig);
 
     // Within refractory of AlertPreserve, but AlertNew should fire since the full bucket has 2.
@@ -2277,7 +2365,7 @@
     SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
 }
 
-TEST_F(ConfigUpdateE2eTest, TestAnomalyDurationMetric) {
+TEST_P(ConfigUpdateE2eTest, TestAnomalyDurationMetric) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
 
@@ -2381,8 +2469,17 @@
     SubscriberReporter::getInstance().setBroadcastSubscriber(key, removeSubId, removeBroadcast);
 
     const sp<UidMap> uidMap = new UidMap();
-    const shared_ptr<StatsService> service =
-            SharedRefBase::make<StatsService>(uidMap, /* queue */ nullptr);
+    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);
     sp<StatsLogProcessor> processor = service->mProcessor;
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(TEN_MINUTES) * 1000000LL;
     int64_t bucketStartTimeNs = processor->mTimeBaseNs;
@@ -2569,6 +2666,9 @@
     SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast);
 
     int64_t updateTimeNs = bucket2StartTimeNs + 50 * NS_PER_SEC;
+    if (GetParam()) {
+        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
@@ -2628,7 +2728,7 @@
     SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
 }
 
-TEST_F(ConfigUpdateE2eTest, TestAlarms) {
+TEST_P(ConfigUpdateE2eTest, TestAlarms) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
     Alarm alarmPreserve = createAlarm("AlarmPreserve", /*offset*/ 5 * MS_PER_SEC,
@@ -2700,7 +2800,7 @@
 
     int64_t startTimeSec = 10;
     sp<StatsLogProcessor> processor = CreateStatsLogProcessor(
-            startTimeSec * NS_PER_SEC, startTimeSec * NS_PER_SEC, config, key);
+            startTimeSec * NS_PER_SEC, startTimeSec * NS_PER_SEC, config, key, mLogEventFilter);
 
     sp<AlarmMonitor> alarmMonitor = processor->getPeriodicAlarmMonitor();
     // First alarm is for alarm preserve's offset of 5 seconds.
@@ -2756,7 +2856,9 @@
                 return Status::ok();
             });
     SubscriberReporter::getInstance().setBroadcastSubscriber(key, newSubId, newBroadcast);
-
+    if (GetParam()) {
+        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.
@@ -2796,7 +2898,7 @@
     SubscriberReporter::getInstance().unsetBroadcastSubscriber(key, newSubId);
 }
 
-TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhat) {
+TEST_P(ConfigUpdateE2eTest, TestNewDurationExistingWhat) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
@@ -2808,8 +2910,8 @@
     ConfigKey key(123, 987);
     uint64_t bucketStartTimeNs = 10000000000;  // 0:10
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL;
-    sp<StatsLogProcessor> processor =
-            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+                                                              config, key, mLogEventFilter);
 
     int app1Uid = 123;
     vector<int> attributionUids1 = {app1Uid};
@@ -2828,6 +2930,11 @@
     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);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, config);
 
     event = CreateReleaseWakelockEvent(bucketStartTimeNs + 80 * NS_PER_SEC, attributionUids1,
@@ -2858,7 +2965,7 @@
     EXPECT_EQ(bucketInfo.duration_nanos(), 20 * NS_PER_SEC);
 }
 
-TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedCondition) {
+TEST_P(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedCondition) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
@@ -2880,8 +2987,8 @@
     ConfigKey key(123, 987);
     uint64_t bucketStartTimeNs = 10000000000;  // 0:10
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL;
-    sp<StatsLogProcessor> processor =
-            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+                                                              config, key, mLogEventFilter);
 
     int app1Uid = 123, app2Uid = 456;
     vector<int> attributionUids1 = {app1Uid};
@@ -2918,6 +3025,11 @@
             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);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, config);
 
     event = CreateMoveToBackgroundEvent(bucketStartTimeNs + 73 * NS_PER_SEC, app2Uid);  // 1:13
@@ -2957,7 +3069,7 @@
     EXPECT_EQ(bucketInfo.duration_nanos(), 17 * NS_PER_SEC);
 }
 
-TEST_F(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedState) {
+TEST_P(ConfigUpdateE2eTest, TestNewDurationExistingWhatSlicedState) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
     *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
@@ -2992,8 +3104,8 @@
     ConfigKey key(123, 987);
     uint64_t bucketStartTimeNs = 10000000000;  // 0:10
     uint64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(FIVE_MINUTES) * 1000000LL;
-    sp<StatsLogProcessor> processor =
-            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+    sp<StatsLogProcessor> processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs,
+                                                              config, key, mLogEventFilter);
 
     int app1Uid = 123, app2Uid = 456;
     vector<int> attributionUids1 = {app1Uid};
@@ -3032,6 +3144,11 @@
             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);
+    }
     processor->OnConfigUpdated(updateTimeNs, key, config);
 
     event = CreateAcquireWakelockEvent(bucketStartTimeNs + 72 * NS_PER_SEC, attributionUids2,
diff --git a/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index 09ce418..4dcef51 100644
--- a/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -237,6 +237,10 @@
     sp<AlarmMonitor> subscriberAlarmMonitor;
     vector<int64_t> activeConfigsBroadcast;
 
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
+
     int broadcastCount = 0;
     StatsLogProcessor processor(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
@@ -249,7 +253,11 @@
                 activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
                                               activeConfigs.end());
                 return true;
-            });
+            },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(CreateAtomIdSetFromConfig(config), &processor))
+            .Times(1);
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);  // 0:00
 
diff --git a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
index 011c28f..474cb7a 100644
--- a/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
+++ b/statsd/tests/e2e/GaugeMetric_e2e_pull_test.cpp
@@ -68,23 +68,7 @@
 
 }  // namespaces
 
-// Setup for test fixture.
-class GaugeMetricE2ePulledTest : public ::testing::TestWithParam<string> {
-    void SetUp() override {
-        FlagProvider::getInstance().overrideFuncs(&isAtLeastSFuncTrue);
-        FlagProvider::getInstance().overrideFlag(LIMIT_PULL_FLAG, GetParam(),
-                                                 /*isBootFlag=*/true);
-    }
-
-    void TearDown() override {
-        FlagProvider::getInstance().resetOverrides();
-    }
-};
-
-INSTANTIATE_TEST_SUITE_P(LimitPull, GaugeMetricE2ePulledTest,
-                         testing::Values(FLAG_FALSE, FLAG_TRUE));
-
-TEST_P(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvents) {
+TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvents) {
     auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE);
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
@@ -224,7 +208,7 @@
     EXPECT_GT(data.bucket_info(5).atom(0).subsystem_sleep_state().time_millis(), 0);
 }
 
-TEST_P(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTrigger) {
+TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTrigger) {
     StatsdConfig config = CreateStatsdConfig(GaugeMetric::FIRST_N_SAMPLES);
     auto gaugeMetric = config.mutable_gauge_metric(0);
     gaugeMetric->set_max_num_gauge_atoms_per_bucket(3);
@@ -346,7 +330,7 @@
             /*eventTimesNs=*/{(int64_t)(configAddedTimeNs + (2 * bucketSizeNs) + 2)});
 }
 
-TEST_P(GaugeMetricE2ePulledTest, TestConditionChangeToTrueSamplePulledEvents) {
+TEST(GaugeMetricE2ePulledTest, TestConditionChangeToTrueSamplePulledEvents) {
     auto config = CreateStatsdConfig(GaugeMetric::CONDITION_CHANGE_TO_TRUE);
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
@@ -444,7 +428,7 @@
     EXPECT_GT(data.bucket_info(2).atom(1).subsystem_sleep_state().time_millis(), 0);
 }
 
-TEST_P(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvent_LateAlarm) {
+TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvent_LateAlarm) {
     auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE);
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
@@ -542,7 +526,7 @@
     EXPECT_GT(data.bucket_info(2).atom(0).subsystem_sleep_state().time_millis(), 0);
 }
 
-TEST_P(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsWithActivation) {
+TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsWithActivation) {
     auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE, /*useCondition=*/false);
 
     int64_t baseTimeNs = getElapsedRealtimeNs();
@@ -721,7 +705,7 @@
     EXPECT_EQ(gaugeMetrics.skipped_size(), 0);
 }
 
-TEST_P(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTriggerWithActivation) {
+TEST(GaugeMetricE2ePulledTest, TestFirstNSamplesPulledNoTriggerWithActivation) {
     StatsdConfig config = CreateStatsdConfig(GaugeMetric::FIRST_N_SAMPLES);
     auto gaugeMetric = config.mutable_gauge_metric(0);
     gaugeMetric->set_max_num_gauge_atoms_per_bucket(2);
@@ -882,7 +866,7 @@
                              {(int64_t)(configAddedTimeNs + (3 * bucketSizeNs) + 50)});
 }
 
-TEST_P(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsNoCondition) {
+TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsNoCondition) {
     auto config = CreateStatsdConfig(GaugeMetric::RANDOM_ONE_SAMPLE, /*useCondition=*/false);
 
     int64_t baseTimeNs = getElapsedRealtimeNs();
diff --git a/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/statsd/tests/e2e/MetricActivation_e2e_test.cpp
index e320419..3c625ac 100644
--- a/statsd/tests/e2e/MetricActivation_e2e_test.cpp
+++ b/statsd/tests/e2e/MetricActivation_e2e_test.cpp
@@ -252,6 +252,9 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
     StatsLogProcessor processor(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
             [](const ConfigKey& key) { return true; },
@@ -263,7 +266,11 @@
                 activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
                                               activeConfigs.end());
                 return true;
-            });
+            },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+    const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
 
@@ -463,6 +470,9 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
     StatsLogProcessor processor(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
             [](const ConfigKey& key) { return true; },
@@ -474,7 +484,11 @@
                 activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
                                               activeConfigs.end());
                 return true;
-            });
+            },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+    const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
 
@@ -784,6 +798,9 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
     StatsLogProcessor processor(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
             [](const ConfigKey& key) { return true; },
@@ -795,7 +812,11 @@
                 activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
                                               activeConfigs.end());
                 return true;
-            });
+            },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+    const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
 
@@ -1117,6 +1138,9 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
     StatsLogProcessor processor(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
             [](const ConfigKey& key) { return true; },
@@ -1128,7 +1152,11 @@
                 activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
                                               activeConfigs.end());
                 return true;
-            });
+            },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+    const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
 
@@ -1314,6 +1342,9 @@
 
     long timeBase1 = 1;
     int broadcastCount = 0;
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter = std::make_shared<MockLogEventFilter>();
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(StatsLogProcessor::getDefaultAtomIdSet(), _))
+            .Times(1);
     StatsLogProcessor processor(
             m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, bucketStartTimeNs,
             [](const ConfigKey& key) { return true; },
@@ -1325,7 +1356,11 @@
                 activeConfigsBroadcast.insert(activeConfigsBroadcast.end(), activeConfigs.begin(),
                                               activeConfigs.end());
                 return true;
-            });
+            },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, mockLogEventFilter);
+
+    const LogEventFilter::AtomIdSet atomIdsList = CreateAtomIdSetFromConfig(config);
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(atomIdsList, &processor)).Times(1);
 
     processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
 
diff --git a/statsd/tests/e2e/RestrictedConfig_e2e_test.cpp b/statsd/tests/e2e/RestrictedConfig_e2e_test.cpp
new file mode 100644
index 0000000..d02a8e5
--- /dev/null
+++ b/statsd/tests/e2e/RestrictedConfig_e2e_test.cpp
@@ -0,0 +1,479 @@
+// 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 <android-modules-utils/sdk_level.h>
+#include <gtest/gtest.h>
+
+#include "flags/FlagProvider.h"
+#include "storage/StorageManager.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using android::modules::sdklevel::IsAtLeastU;
+
+#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;
+    config.add_allowed_log_source("AID_ROOT");
+    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;
+    config.add_allowed_log_source("AID_ROOT");
+    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();
+        service->mUidMap->updateMap(
+                startTimeNs, {delegateUid, kCallingUid},
+                /*versionCode=*/{1, 1}, /*versionString=*/{String16("v2"), String16("v2")},
+                {String16(delegatePackageName.c_str()), String16(configPackageName.c_str())},
+                /*installer=*/{String16(), String16()}, /*certificateHash=*/{{}, {}});
+    }
+    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.
+    config.clear_allowed_log_source();
+    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
\ No newline at end of file
diff --git a/statsd/tests/e2e/RestrictedEventMetric_e2e_test.cpp b/statsd/tests/e2e/RestrictedEventMetric_e2e_test.cpp
new file mode 100644
index 0000000..a45a88e
--- /dev/null
+++ b/statsd/tests/e2e/RestrictedEventMetric_e2e_test.cpp
@@ -0,0 +1,1193 @@
+// 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 <android-modules-utils/sdk_level.h>
+#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 android::modules::sdklevel::IsAtLeastU;
+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();
+                }));
+
+        config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+        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, String16(delegate_package_name.c_str()),
+                          /*uid=*/delegate_uid, /*versionCode=*/1,
+                          /*versionString=*/String16("v2"),
+                          /*installer=*/String16(""), /*certificateHash=*/{});
+        uidMap->updateApp(configAddedTimeNs + 1, String16(config_package_name.c_str()),
+                          /*uid=*/config_app_uid, /*versionCode=*/1,
+                          /*versionString=*/String16("v2"),
+                          /*installer=*/String16(""), /*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, String16(delegate_package_name.c_str()),
+                      /*uid=*/delegate_uid + 1, /*versionCode=*/1,
+                      /*versionString=*/String16("v2"),
+                      /*installer=*/String16(""), /*certificateHash=*/{});
+    uidMap->updateApp(configAddedTimeNs + 1, String16(config_package_name.c_str()),
+                      /*uid=*/config_app_uid + 1, /*versionCode=*/1,
+                      /*versionString=*/String16("v2"),
+                      /*installer=*/String16(""), /*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, String16(delegate_package_name.c_str()),
+                      /*uid=*/delegate_uid + 1, /*versionCode=*/1,
+                      /*versionString=*/String16("v2"),
+                      /*installer=*/String16(""), /*certificateHash=*/{});
+    uidMap->updateApp(configAddedTimeNs + 1, String16(config_package_name.c_str()),
+                      /*uid=*/config_app_uid + 1, /*versionCode=*/1,
+                      /*versionString=*/String16("v2"),
+                      /*installer=*/String16(""), /*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, String16("new.delegate.package"),
+                      /*uid=*/newDelegateUid, /*versionCode=*/1,
+                      /*versionString=*/String16("v2"),
+                      /*installer=*/String16(""), /*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/ValueMetric_pull_e2e_test.cpp b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index 810b550..1d8307b 100644
--- a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -17,7 +17,6 @@
 
 #include <vector>
 
-#include "flags/FlagProvider.h"
 #include "src/StatsLogProcessor.h"
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
@@ -144,20 +143,6 @@
 
 }  // namespace
 
-// Setup for test fixture.
-class ValueMetricE2eTest : public testing::TestWithParam<string> {
-    void SetUp() override {
-        FlagProvider::getInstance().overrideFlag(LIMIT_PULL_FLAG, GetParam(),
-                                                 /*isBootFlag=*/true);
-    }
-
-    void TearDown() override {
-        FlagProvider::getInstance().resetOverrides();
-    }
-};
-
-INSTANTIATE_TEST_SUITE_P(LimitPull, ValueMetricE2eTest, testing::Values(FLAG_FALSE, FLAG_TRUE));
-
 /**
  * Tests the initial condition and condition after the first log events for
  * value metrics with either a combination condition or simple condition.
@@ -217,7 +202,7 @@
     EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition);
 }
 
-TEST_P(ValueMetricE2eTest, TestPulledEvents) {
+TEST(ValueMetricE2eTest, TestPulledEvents) {
     auto config = CreateStatsdConfig();
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
@@ -337,7 +322,7 @@
               skipped.end_bucket_elapsed_nanos());
 }
 
-TEST_P(ValueMetricE2eTest, TestPulledEvents_LateAlarm) {
+TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm) {
     auto config = CreateStatsdConfig();
     int64_t baseTimeNs = getElapsedRealtimeNs();
     // 10 mins == 2 bucket durations.
@@ -464,7 +449,7 @@
               skipped.end_bucket_elapsed_nanos());
 }
 
-TEST_P(ValueMetricE2eTest, TestPulledEvents_WithActivation) {
+TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation) {
     auto config = CreateStatsdConfig(false);
     int64_t baseTimeNs = getElapsedRealtimeNs();
     int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
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 22c5440..a2f3280 100644
--- a/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -327,11 +327,11 @@
     StatsdStats stats;
     time_t now = time(nullptr);
     // old event, we get it from the stats buffer. should be ignored.
-    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, 1000);
+    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, 1000, false);
 
-    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 1);
-    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 2);
-    stats.noteAtomLogged(util::APP_CRASH_OCCURRED, now + 3);
+    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 1, false);
+    stats.noteAtomLogged(util::SENSOR_STATE_CHANGED, now + 2, false);
+    stats.noteAtomLogged(util::APP_CRASH_OCCURRED, now + 3, false);
 
     vector<uint8_t> output;
     stats.dumpStats(&output, false);
@@ -351,6 +351,7 @@
             dropboxAtomGood = true;
         }
         EXPECT_FALSE(atomStats.has_dropped_count());
+        EXPECT_FALSE(atomStats.has_skip_count());
     }
 
     EXPECT_TRUE(dropboxAtomGood);
@@ -363,9 +364,9 @@
     int newAtom1 = StatsdStats::kMaxPushedAtomId + 1;
     int newAtom2 = StatsdStats::kMaxPushedAtomId + 2;
 
-    stats.noteAtomLogged(newAtom1, now + 1);
-    stats.noteAtomLogged(newAtom1, now + 2);
-    stats.noteAtomLogged(newAtom2, now + 3);
+    stats.noteAtomLogged(newAtom1, now + 1, false);
+    stats.noteAtomLogged(newAtom1, now + 2, false);
+    stats.noteAtomLogged(newAtom2, now + 3, false);
 
     vector<uint8_t> output;
     stats.dumpStats(&output, false);
@@ -385,6 +386,7 @@
             newAtom2Good = true;
         }
         EXPECT_FALSE(atomStats.has_dropped_count());
+        EXPECT_FALSE(atomStats.has_skip_count());
     }
 
     EXPECT_TRUE(newAtom1Good);
@@ -479,6 +481,96 @@
     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.noteRestrictedConfigDbSize(key, 999, 111);
+
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+    StatsdStatsReport report;
+    bool good = report.ParseFromArray(&output[0], output.size());
+    EXPECT_TRUE(good);
+
+    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());
+}
+
+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");
+
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+    StatsdStatsReport report;
+    bool good = report.ParseFromArray(&output[0], output.size());
+    EXPECT_TRUE(good);
+
+    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();
@@ -619,7 +711,7 @@
         // We must call noteAtomLogged as well because only those pushed atoms
         // that have been logged will have stats printed about them in the
         // proto.
-        stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0);
+        stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0, false);
         stats.noteAtomError(pushAtomTag, /*pull=*/false);
 
         stats.noteAtomError(pullAtomTag, /*pull=*/true);
@@ -636,6 +728,7 @@
     EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
     EXPECT_EQ(numErrors, pushedAtomStats.error_count());
     EXPECT_FALSE(pushedAtomStats.has_dropped_count());
+    EXPECT_FALSE(pushedAtomStats.has_skip_count());
 
     // Check error count = numErrors for pull atom
     ASSERT_EQ(1, report.pulled_atom_stats_size());
@@ -651,10 +744,9 @@
     const int nonPlatformPushAtomTag = StatsdStats::kMaxPushedAtomId + 100;
 
     const int numDropped = 10;
-
     for (int i = 0; i < numDropped; i++) {
-        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag);
-        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag);
+        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag, false);
+        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag, false);
     }
 
     vector<uint8_t> output;
@@ -672,15 +764,17 @@
     EXPECT_EQ(numDropped, pushedAtomStats.count());
     EXPECT_EQ(numDropped, pushedAtomStats.dropped_count());
     EXPECT_FALSE(pushedAtomStats.has_error_count());
+    EXPECT_FALSE(pushedAtomStats.has_skip_count());
 
     const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
     EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
     EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.count());
     EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.dropped_count());
     EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
+    EXPECT_FALSE(nonPlatformPushedAtomStats.has_skip_count());
 }
 
-TEST(StatsdStatsTest, TestAtomDroppedAndLoggedStats) {
+TEST(StatsdStatsTest, TestAtomLoggedAndDroppedStats) {
     StatsdStats stats;
 
     const int pushAtomTag = 100;
@@ -688,14 +782,14 @@
 
     const int numLogged = 10;
     for (int i = 0; i < numLogged; i++) {
-        stats.noteAtomLogged(pushAtomTag, /*timeSec*/ 0);
-        stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec*/ 0);
+        stats.noteAtomLogged(pushAtomTag, /*timeSec*/ 0, false);
+        stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec*/ 0, false);
     }
 
     const int numDropped = 10;
     for (int i = 0; i < numDropped; i++) {
-        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag);
-        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag);
+        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag, false);
+        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag, false);
     }
 
     vector<uint8_t> output;
@@ -711,12 +805,99 @@
     EXPECT_EQ(numLogged + numDropped, pushedAtomStats.count());
     EXPECT_EQ(numDropped, pushedAtomStats.dropped_count());
     EXPECT_FALSE(pushedAtomStats.has_error_count());
+    EXPECT_FALSE(pushedAtomStats.has_skip_count());
 
     const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
     EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
     EXPECT_EQ(numLogged + numDropped, nonPlatformPushedAtomStats.count());
     EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.dropped_count());
     EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
+    EXPECT_FALSE(nonPlatformPushedAtomStats.has_skip_count());
+}
+
+TEST(StatsdStatsTest, TestAtomSkippedStats) {
+    StatsdStats stats;
+
+    const int pushAtomTag = 100;
+    const int nonPlatformPushAtomTag = StatsdStats::kMaxPushedAtomId + 100;
+    const int numSkipped = 10;
+
+    for (int i = 0; i < numSkipped; i++) {
+        stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0, /*isSkipped*/ true);
+        stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec=*/0, /*isSkipped*/ true);
+    }
+
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+    StatsdStatsReport report;
+    EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
+
+    // Check skip_count = numSkipped for push atoms
+    ASSERT_EQ(2, report.atom_stats_size());
+
+    const auto& pushedAtomStats = report.atom_stats(0);
+    EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
+    EXPECT_EQ(numSkipped, pushedAtomStats.count());
+    EXPECT_EQ(numSkipped, pushedAtomStats.skip_count());
+    EXPECT_FALSE(pushedAtomStats.has_error_count());
+
+    const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
+    EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
+    EXPECT_EQ(numSkipped, nonPlatformPushedAtomStats.count());
+    EXPECT_EQ(numSkipped, nonPlatformPushedAtomStats.skip_count());
+    EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
+}
+
+TEST(StatsdStatsTest, TestAtomLoggedAndDroppedAndSkippedStats) {
+    StatsdStats stats;
+
+    const int pushAtomTag = 100;
+    const int nonPlatformPushAtomTag = StatsdStats::kMaxPushedAtomId + 100;
+
+    const int numLogged = 10;
+    for (int i = 0; i < numLogged; i++) {
+        stats.noteAtomLogged(pushAtomTag, /*timeSec*/ 0, false);
+        stats.noteAtomLogged(nonPlatformPushAtomTag, /*timeSec*/ 0, false);
+    }
+
+    const int numDropped = 10;
+    for (int i = 0; i < numDropped; i++) {
+        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, pushAtomTag, true);
+        stats.noteEventQueueOverflow(/*oldestEventTimestampNs*/ 0, nonPlatformPushAtomTag, true);
+    }
+
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+    StatsdStatsReport report;
+    EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
+
+    // Check dropped_count = numDropped for push atoms
+    ASSERT_EQ(2, report.atom_stats_size());
+
+    const auto& pushedAtomStats = report.atom_stats(0);
+    EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
+    EXPECT_EQ(numLogged + numDropped, pushedAtomStats.count());
+    EXPECT_EQ(numDropped, pushedAtomStats.dropped_count());
+    EXPECT_EQ(numDropped, pushedAtomStats.skip_count());
+    EXPECT_FALSE(pushedAtomStats.has_error_count());
+
+    const auto& nonPlatformPushedAtomStats = report.atom_stats(1);
+    EXPECT_EQ(nonPlatformPushAtomTag, nonPlatformPushedAtomStats.tag());
+    EXPECT_EQ(numLogged + numDropped, nonPlatformPushedAtomStats.count());
+    EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.dropped_count());
+    EXPECT_EQ(numDropped, nonPlatformPushedAtomStats.skip_count());
+    EXPECT_FALSE(nonPlatformPushedAtomStats.has_error_count());
+}
+
+TEST(StatsdStatsTest, TestShardOffsetProvider) {
+    StatsdStats stats;
+    ShardOffsetProvider::getInstance().setShardOffset(15);
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+
+    StatsdStatsReport report;
+    EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
+    EXPECT_EQ(report.shard_offset(), 15);
 }
 
 }  // namespace statsd
diff --git a/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp b/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp
new file mode 100644
index 0000000..b6c4545
--- /dev/null
+++ b/statsd/tests/metrics/RestrictedEventMetricProducer_test.cpp
@@ -0,0 +1,266 @@
+#include "src/metrics/RestrictedEventMetricProducer.h"
+
+#include <android-modules-utils/sdk_level.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 {
+
+using android::modules::sdklevel::IsAtLeastU;
+
+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);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0);
+    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);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0);
+    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"));
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/0,
+                                           /*initialConditionCache=*/{ConditionState::kUnknown},
+                                           new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0);
+    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);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0);
+    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);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0);
+    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);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0);
+
+    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);
+    RestrictedEventMetricProducer producer(configKey, metric,
+                                           /*conditionIndex=*/-1,
+                                           /*initialConditionCache=*/{}, new ConditionWizard(),
+                                           /*protoHash=*/0x1234567890,
+                                           /*startTimeNs=*/0);
+
+    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
\ No newline at end of file
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..d7374a5 100644
--- a/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -4104,6 +4104,44 @@
                                                      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;
+
+    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),
+              InvalidConfigReason(INVALID_CONFIG_REASON_RESTRICTED_METRIC_NOT_SUPPORTED));
+}
+
 }  // 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..7dd695c 100644
--- a/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
+++ b/statsd/tests/metrics/parsing_utils/metrics_manager_util_test.cpp
@@ -1764,6 +1764,51 @@
                                 metric.id()));
 }
 
+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));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/shell/ShellSubscriber_test.cpp b/statsd/tests/shell/ShellSubscriber_test.cpp
index 2960cae..a994966 100644
--- a/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -14,24 +14,43 @@
 
 #include "src/shell/ShellSubscriber.h"
 
+#include <aidl/android/os/StatsSubscriptionCallbackReason.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
 #include <unistd.h>
 
+#include <optional>
 #include <vector>
 
 #include "frameworks/proto_logging/stats/atoms.pb.h"
+#include "gtest_matchers.h"
 #include "src/shell/shell_config.pb.h"
 #include "src/shell/shell_data.pb.h"
 #include "stats_event.h"
+#include "statslog_statsdtest.h"
 #include "tests/metrics/metrics_test_helper.h"
 #include "tests/statsd_test_util.h"
 
+using ::aidl::android::os::StatsSubscriptionCallbackReason;
 using android::sp;
+using android::os::statsd::TestAtomReported;
+using android::os::statsd::TrainExperimentIds;
+using android::os::statsd::util::BytesField;
+using android::os::statsd::util::CPU_ACTIVE_TIME;
+using android::os::statsd::util::PHONE_SIGNAL_STRENGTH_CHANGED;
+using android::os::statsd::util::PLUGGED_STATE_CHANGED;
+using android::os::statsd::util::SCREEN_STATE_CHANGED;
+using android::os::statsd::util::TEST_ATOM_REPORTED;
 using std::vector;
 using testing::_;
+using testing::A;
+using testing::ByMove;
+using testing::DoAll;
 using testing::Invoke;
 using testing::NaggyMock;
+using testing::Return;
+using testing::SaveArg;
+using testing::SetArgPointee;
 using testing::StrictMock;
 
 namespace android {
@@ -48,6 +67,8 @@
 int kCpuTime1 = 100;
 int kCpuTime2 = 200;
 
+int64_t kCpuActiveTimeEventTimestampNs = 1111L;
+
 // Number of clients running simultaneously
 
 // Just a single client
@@ -61,10 +82,12 @@
     auto* atom1 = shellData.add_atom()->mutable_cpu_active_time();
     atom1->set_uid(kUid1);
     atom1->set_time_millis(kCpuTime1);
+    shellData.add_elapsed_timestamp_nanos(kCpuActiveTimeEventTimestampNs);
 
     auto* atom2 = shellData.add_atom()->mutable_cpu_active_time();
     atom2->set_uid(kUid2);
     atom2->set_time_millis(kCpuTime2);
+    shellData.add_elapsed_timestamp_nanos(kCpuActiveTimeEventTimestampNs);
 
     return shellData;
 }
@@ -73,7 +96,7 @@
 ShellSubscription getPulledConfig() {
     ShellSubscription config;
     auto* pull_config = config.add_pulled();
-    pull_config->mutable_matcher()->set_atom_id(10016);
+    pull_config->mutable_matcher()->set_atom_id(CPU_ACTIVE_TIME);
     pull_config->set_freq_millis(2000);
     return config;
 }
@@ -81,8 +104,8 @@
 // Utility to adjust CPU time for pulled events
 shared_ptr<LogEvent> makeCpuActiveTimeAtom(int32_t uid, int64_t timeMillis) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, 10016);
-    AStatsEvent_overwriteTimestamp(statsEvent, 1111L);
+    AStatsEvent_setAtomId(statsEvent, CPU_ACTIVE_TIME);
+    AStatsEvent_overwriteTimestamp(statsEvent, kCpuActiveTimeEventTimestampNs);
     AStatsEvent_writeInt32(statsEvent, uid);
     AStatsEvent_writeInt64(statsEvent, timeMillis);
 
@@ -110,8 +133,8 @@
     return pushedList;
 }
 
-// Utility to read & return a string representing ShellData, skipping heartbeats.
-string readData(int fd) {
+// Utility to read & return ShellData proto, skipping heartbeats.
+static ShellData readData(int fd) {
     ssize_t dataSize = 0;
     while (dataSize == 0) {
         read(fd, &dataSize, sizeof(dataSize));
@@ -123,14 +146,15 @@
     // Make sure the received bytes can be parsed to an atom.
     ShellData receivedAtom;
     EXPECT_TRUE(receivedAtom.ParseFromArray(dataBuffer.data(), dataSize) != 0);
-    return string(dataBuffer.begin(), dataBuffer.end());
+    return receivedAtom;
 }
 
 void runShellTest(ShellSubscription config, sp<MockUidMap> uidMap,
                   sp<MockStatsPullerManager> pullerManager,
                   const vector<std::shared_ptr<LogEvent>>& pushedEvents,
                   const vector<ShellData>& expectedData, int numClients) {
-    sp<ShellSubscriber> shellManager = new ShellSubscriber(uidMap, pullerManager);
+    sp<ShellSubscriber> shellManager =
+            new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
 
     size_t bufferSize = config.ByteSize();
     vector<uint8_t> buffer(bufferSize);
@@ -159,32 +183,441 @@
         shellManager->onLogEvent(*event);
     }
 
-    // Because we might receive heartbeats from statsd, consisting of data sizes
-    // of 0, encapsulate reads within a while loop.
-    vector<string> expectedDataSerialized;
-    for (size_t i = 0; i < expectedData.size(); i++) {
-        expectedDataSerialized.push_back(expectedData[i].SerializeAsString());
-    }
-
     for (int i = 0; i < numClients; i++) {
-        vector<string> expectedDataCopy(expectedDataSerialized);
-
-        while (expectedDataCopy.size() > 1) {
-            // Use readData utility to read data in string format.
-            string dataString = readData(fds_datas[i][0]);
-
-            // Search for string within the expected data vector
-            auto it = find(expectedDataCopy.begin(), expectedDataCopy.end(), dataString);
-            EXPECT_NE(it, expectedDataCopy.end());
-            expectedDataCopy.erase(it);
+        vector<ShellData> actualData;
+        for (int j = 1; j <= expectedData.size(); j++) {
+            actualData.push_back(readData(fds_datas[i][0]));
         }
+
+        EXPECT_THAT(expectedData, UnorderedPointwise(EqShellData(), actualData));
     }
 
     // Not closing fds_datas[i][0] because this causes writes within ShellSubscriberClient to hang
 }
 
+unique_ptr<LogEvent> createTestAtomReportedEvent(const uint64_t timestampNs,
+                                                 const int32_t intFieldValue,
+                                                 const vector<int64_t>& expIds) {
+    TrainExperimentIds trainExpIds;
+    *trainExpIds.mutable_experiment_id() = {expIds.begin(), expIds.end()};
+    const vector<uint8_t> trainExpIdsBytes = protoToBytes(trainExpIds);
+    return CreateTestAtomReportedEvent(
+            timestampNs, /* attributionUids */ {1001},
+            /* attributionTags */ {"app1"}, intFieldValue, /*longField */ 0LL,
+            /* floatField */ 0.0f,
+            /* stringField */ "abc", /* boolField */ false, TestAtomReported::OFF, trainExpIdsBytes,
+            /* repeatedIntField */ {}, /* repeatedLongField */ {}, /* repeatedFloatField */ {},
+            /* repeatedStringField */ {}, /* repeatedBoolField */ {},
+            /* repeatedBoolFieldLength */ 0, /* repeatedEnumField */ {});
+}
+
+TestAtomReported createTestAtomReportedProto(const int32_t intFieldValue,
+                                             const vector<int64_t>& expIds) {
+    TestAtomReported t;
+    auto* attributionNode = t.add_attribution_node();
+    attributionNode->set_uid(1001);
+    attributionNode->set_tag("app1");
+    t.set_int_field(intFieldValue);
+    t.set_long_field(0);
+    t.set_float_field(0.0f);
+    t.set_string_field("abc");
+    t.set_boolean_field(false);
+    t.set_state(TestAtomReported_State_OFF);
+    *t.mutable_bytes_field()->mutable_experiment_id() = {expIds.begin(), expIds.end()};
+    return t;
+}
+
+class ShellSubscriberCallbackTest : public ::testing::Test {
+protected:
+    ShellSubscriberCallbackTest()
+        : uidMap(new NaggyMock<MockUidMap>()),
+          pullerManager(new StrictMock<MockStatsPullerManager>()),
+          mockLogEventFilter(std::make_shared<MockLogEventFilter>()),
+          shellSubscriber(uidMap, pullerManager, mockLogEventFilter),
+          callback(SharedRefBase::make<StrictMock<MockStatsSubscriptionCallback>>()),
+          reason(nullopt) {
+    }
+
+    void SetUp() override {
+        // Save callback arguments when it is invoked.
+        ON_CALL(*callback, onSubscriptionData(_, _))
+                .WillByDefault(DoAll(SaveArg<0>(&reason), SaveArg<1>(&payload),
+                                     Return(ByMove(Status::ok()))));
+
+        ShellSubscription config;
+        config.add_pushed()->set_atom_id(TEST_ATOM_REPORTED);
+        config.add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
+        config.add_pushed()->set_atom_id(PHONE_SIGNAL_STRENGTH_CHANGED);
+        configBytes = protoToBytes(config);
+    }
+
+    void TearDown() override {
+        // Expect empty call from the shellSubscriber destructor
+        LogEventFilter::AtomIdSet tagIds;
+        EXPECT_CALL(*mockLogEventFilter, setAtomIds(tagIds, &shellSubscriber)).Times(1);
+    }
+
+    sp<MockUidMap> uidMap;
+    sp<MockStatsPullerManager> pullerManager;
+    std::shared_ptr<MockLogEventFilter> mockLogEventFilter;
+    ShellSubscriber shellSubscriber;
+    std::shared_ptr<MockStatsSubscriptionCallback> callback;
+    vector<uint8_t> configBytes;
+
+    // Capture callback arguments.
+    std::optional<StatsSubscriptionCallbackReason> reason;
+    vector<uint8_t> payload;
+};
+
+class ShellSubscriberCallbackPulledTest : public ShellSubscriberCallbackTest {
+protected:
+    void SetUp() override {
+        ShellSubscriberCallbackTest::SetUp();
+
+        const vector<int32_t> uids{AID_SYSTEM};
+        const vector<std::shared_ptr<LogEvent>> pulledData{
+                makeCpuActiveTimeAtom(/*uid=*/kUid1, /*timeMillis=*/kCpuTime1),
+                makeCpuActiveTimeAtom(/*uid=*/kUid2, /*timeMillis=*/kCpuTime2)};
+        ON_CALL(*pullerManager, Pull(CPU_ACTIVE_TIME, uids, _, _))
+                .WillByDefault(DoAll(SetArgPointee<3>(pulledData), Return(true)));
+
+        configBytes = protoToBytes(getPulledConfig());
+
+        // Used to call pullAndSendHeartbeatsIfNeeded directly without depending on sleep.
+        shellSubscriberClient = std::move(ShellSubscriberClient::create(
+                configBytes, callback, /* startTimeSec= */ 0, uidMap, pullerManager));
+    }
+
+    unique_ptr<ShellSubscriberClient> shellSubscriberClient;
+};
+
+LogEventFilter::AtomIdSet CreateAtomIdSetFromShellSubscriptionBytes(const vector<uint8_t>& bytes) {
+    LogEventFilter::AtomIdSet result;
+
+    ShellSubscription config;
+    config.ParseFromArray(bytes.data(), bytes.size());
+
+    for (int i = 0; i < config.pushed_size(); i++) {
+        const auto& pushed = config.pushed(i);
+        EXPECT_TRUE(pushed.has_atom_id());
+        result.insert(pushed.atom_id());
+    }
+
+    return result;
+}
+
 }  // namespace
 
+TEST_F(ShellSubscriberCallbackTest, testAddSubscription) {
+    EXPECT_CALL(
+            *mockLogEventFilter,
+            setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+            .Times(1);
+    EXPECT_TRUE(shellSubscriber.startNewSubscription(configBytes, callback));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testAddSubscriptionExceedMax) {
+    const size_t maxSubs = ShellSubscriber::getMaxSubscriptions();
+    EXPECT_CALL(
+            *mockLogEventFilter,
+            setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+            .Times(maxSubs);
+    vector<bool> results(maxSubs, false);
+    for (int i = 0; i < maxSubs; i++) {
+        results[i] = shellSubscriber.startNewSubscription(configBytes, callback);
+    }
+
+    // First maxSubs subscriptions should succeed.
+    EXPECT_THAT(results, Each(IsTrue()));
+
+    // Subsequent startNewSubscription should fail.
+    EXPECT_FALSE(shellSubscriber.startNewSubscription(configBytes, callback));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testPushedEventsAreCached) {
+    // Expect callback to not be invoked
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(0));
+    EXPECT_CALL(
+            *mockLogEventFilter,
+            setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+            .Times(1);
+    shellSubscriber.startNewSubscription(configBytes, callback);
+
+    // Log an event that does NOT invoke the callack.
+    shellSubscriber.onLogEvent(*CreateScreenStateChangedEvent(
+            1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testOverflowCacheIsFlushed) {
+    // Expect callback to be invoked once.
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+    EXPECT_CALL(
+            *mockLogEventFilter,
+            setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+            .Times(1);
+    shellSubscriber.startNewSubscription(configBytes, callback);
+
+    shellSubscriber.onLogEvent(*CreateScreenStateChangedEvent(
+            1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON));
+
+    // Inflate size of TestAtomReported through the MODE_BYTES field.
+    const vector<int64_t> expIds = vector<int64_t>(200, INT64_MAX);
+
+    // This event should trigger cache overflow flush.
+    shellSubscriber.onLogEvent(*createTestAtomReportedEvent(/*timestampNs=*/1100,
+                                                            /*intFieldValue=*/1, expIds));
+
+    EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::STATSD_INITIATED));
+
+    // Get ShellData proto from the bytes payload of the callback.
+    ShellData actualShellData;
+    ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+    ShellData expectedShellData;
+    expectedShellData.add_atom()->mutable_screen_state_changed()->set_state(
+            ::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    *expectedShellData.add_atom()->mutable_test_atom_reported() =
+            createTestAtomReportedProto(/* intFieldValue=*/1, expIds);
+    expectedShellData.add_elapsed_timestamp_nanos(1000);
+    expectedShellData.add_elapsed_timestamp_nanos(1100);
+
+    EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testFlushTrigger) {
+    // Expect callback to be invoked once.
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+    EXPECT_CALL(
+            *mockLogEventFilter,
+            setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+            .Times(1);
+    shellSubscriber.startNewSubscription(configBytes, callback);
+
+    shellSubscriber.onLogEvent(*CreateScreenStateChangedEvent(
+            1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON));
+
+    shellSubscriber.flushSubscription(callback);
+
+    EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::FLUSH_REQUESTED));
+
+    // Get ShellData proto from the bytes payload of the callback.
+    ShellData actualShellData;
+    ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+    ShellData expectedShellData;
+    expectedShellData.add_atom()->mutable_screen_state_changed()->set_state(
+            ::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    expectedShellData.add_elapsed_timestamp_nanos(1000);
+
+    EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testFlushTriggerEmptyCache) {
+    // Expect callback to be invoked once.
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+    EXPECT_CALL(
+            *mockLogEventFilter,
+            setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+            .Times(1);
+    shellSubscriber.startNewSubscription(configBytes, callback);
+
+    shellSubscriber.flushSubscription(callback);
+
+    EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::FLUSH_REQUESTED));
+
+    // Get ShellData proto from the bytes payload of the callback.
+    ShellData actualShellData;
+    ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+    ShellData expectedShellData;
+
+    EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testUnsubscribe) {
+    // Expect callback to be invoked once.
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+    Expectation newSubcriptionEvent =
+            EXPECT_CALL(*mockLogEventFilter,
+                        setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes),
+                                   &shellSubscriber))
+                    .Times(1);
+    LogEventFilter::AtomIdSet idSetEmpty;
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(idSetEmpty, &shellSubscriber))
+            .Times(1)
+            .After(newSubcriptionEvent);
+
+    shellSubscriber.startNewSubscription(configBytes, callback);
+
+    shellSubscriber.onLogEvent(*CreateScreenStateChangedEvent(
+            1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON));
+
+    shellSubscriber.unsubscribe(callback);
+
+    EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::SUBSCRIPTION_ENDED));
+
+    // Get ShellData proto from the bytes payload of the callback.
+    ShellData actualShellData;
+    ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+    ShellData expectedShellData;
+    expectedShellData.add_atom()->mutable_screen_state_changed()->set_state(
+            ::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    expectedShellData.add_elapsed_timestamp_nanos(1000);
+
+    EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+
+    // This event is ignored as the subscription has ended.
+    shellSubscriber.onLogEvent(*CreateScreenStateChangedEvent(
+            1000 /*timestamp*/, ::android::view::DisplayStateEnum::DISPLAY_STATE_ON));
+
+    // This should be a no-op as we've already unsubscribed.
+    shellSubscriber.unsubscribe(callback);
+}
+
+TEST_F(ShellSubscriberCallbackTest, testUnsubscribeEmptyCache) {
+    // Expect callback to be invoked once.
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+    Expectation newSubcriptionEvent =
+            EXPECT_CALL(*mockLogEventFilter,
+                        setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes),
+                                   &shellSubscriber))
+                    .Times(1);
+    LogEventFilter::AtomIdSet idSetEmpty;
+    EXPECT_CALL(*mockLogEventFilter, setAtomIds(idSetEmpty, &shellSubscriber))
+            .Times(1)
+            .After(newSubcriptionEvent);
+
+    shellSubscriber.startNewSubscription(configBytes, callback);
+
+    shellSubscriber.unsubscribe(callback);
+
+    EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::SUBSCRIPTION_ENDED));
+
+    // Get ShellData proto from the bytes payload of the callback.
+    ShellData actualShellData;
+    ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+    ShellData expectedShellData;
+
+    EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+}
+
+TEST_F(ShellSubscriberCallbackTest, testTruncateTimestampAtom) {
+    // Expect callback to be invoked once.
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+    EXPECT_CALL(
+            *mockLogEventFilter,
+            setAtomIds(CreateAtomIdSetFromShellSubscriptionBytes(configBytes), &shellSubscriber))
+            .Times(1);
+    shellSubscriber.startNewSubscription(configBytes, callback);
+
+    shellSubscriber.onLogEvent(*CreatePhoneSignalStrengthChangedEvent(
+            NS_PER_SEC * 5 * 60 + 1000 /*timestamp*/,
+            ::android::telephony::SignalStrengthEnum::SIGNAL_STRENGTH_GOOD));
+
+    shellSubscriber.flushSubscription(callback);
+
+    // Get ShellData proto from the bytes payload of the callback.
+    ShellData actualShellData;
+    ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+    ShellData expectedShellData;
+    expectedShellData.add_atom()->mutable_phone_signal_strength_changed()->set_signal_strength(
+            ::android::telephony::SignalStrengthEnum::SIGNAL_STRENGTH_GOOD);
+    expectedShellData.add_elapsed_timestamp_nanos(NS_PER_SEC * 5 * 60);
+
+    EXPECT_THAT(actualShellData, EqShellData(expectedShellData));
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testPullIfNeededBeforeInterval) {
+    // Pull should not happen
+    EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(0));
+
+    // Expect callback to not be invoked.
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(0));
+
+    shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(/* nowSecs= */ 0, /* nowMillis= */ 0,
+                                                         /* nowNanos= */ 0);
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testPullAtInterval) {
+    // Pull should happen once. The data is cached.
+    EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(1));
+
+    // Expect callback to not be invoked.
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(0));
+
+    // This pull should NOT trigger a cache flush.
+    shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(/* nowSecs= */ 61, /* nowMillis= */ 61'000,
+                                                         /* nowNanos= */ 61'000'000'000);
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testCachedPullIsFlushed) {
+    // Pull should happen once. The data is cached.
+    EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(1));
+
+    // This pull should NOT trigger a cache flush.
+    shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(/* nowSecs= */ 61, /* nowMillis= */ 61'000,
+                                                         /* nowNanos= */ 61'000'000'000);
+
+    // Expect callback to be invoked once flush is requested.
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+
+    // This should flush out data cached from the pull.
+    shellSubscriberClient->flush();
+
+    EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::FLUSH_REQUESTED));
+
+    // Get ShellData proto from the bytes payload of the callback.
+    ShellData actualShellData;
+    ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+    EXPECT_THAT(actualShellData, EqShellData(getExpectedPulledData()));
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testPullAtCacheTimeout) {
+    // Pull should happen once. The data is flushed.
+    EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(1));
+
+    // Expect callback to be invoked.
+    EXPECT_CALL(*callback, onSubscriptionData(_, _)).Times(Exactly(1));
+
+    // This pull should trigger a cache flush.
+    shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(/* nowSecs= */ 70, /* nowMillis= */ 70'000,
+                                                         /* nowNanos= */ 70'000'000'000);
+
+    EXPECT_THAT(reason, Eq(StatsSubscriptionCallbackReason::STATSD_INITIATED));
+
+    // Get ShellData proto from the bytes payload of the callback.
+    ShellData actualShellData;
+    ASSERT_TRUE(actualShellData.ParseFromArray(payload.data(), payload.size()));
+
+    EXPECT_THAT(actualShellData, EqShellData(getExpectedPulledData()));
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testPullFrequencyTooShort) {
+    // Pull should NOT happen.
+    EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(0));
+
+    // This should not trigger a pull even though the timestamp passed in matches the pull interval
+    // specified in the config.
+    const int64_t sleepTimeMs =
+            shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(2, 2000, 2'000'000'000);
+}
+
+TEST_F(ShellSubscriberCallbackPulledTest, testMinSleep) {
+    // Pull should NOT happen.
+    EXPECT_CALL(*pullerManager, Pull(_, A<const vector<int32_t>&>(), _, _)).Times(Exactly(0));
+
+    const int64_t sleepTimeMs =
+            shellSubscriberClient->pullAndSendHeartbeatsIfNeeded(59, 59'000, 59'000'000'000);
+
+    // Even though there is only 1000 ms left until the next pull, the sleep time returned is
+    // kMinCallbackSleepIntervalMs.
+    EXPECT_THAT(sleepTimeMs, Eq(ShellSubscriberClient::kMinCallbackSleepIntervalMs));
+}
+
 TEST(ShellSubscriberTest, testPushedSubscription) {
     sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
@@ -193,24 +626,27 @@
 
     // create a simple config to get screen events
     ShellSubscription config;
-    config.add_pushed()->set_atom_id(29);
+    config.add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
 
     // this is the expected screen event atom.
     vector<ShellData> expectedData;
     ShellData shellData1;
     shellData1.add_atom()->mutable_screen_state_changed()->set_state(
             ::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    shellData1.add_elapsed_timestamp_nanos(pushedList[0]->GetElapsedTimestampNs());
     ShellData shellData2;
     shellData2.add_atom()->mutable_screen_state_changed()->set_state(
             ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+    shellData2.add_elapsed_timestamp_nanos(pushedList[1]->GetElapsedTimestampNs());
     expectedData.push_back(shellData1);
     expectedData.push_back(shellData2);
 
     // Test with single client
-    runShellTest(config, uidMap, pullerManager, pushedList, expectedData, kSingleClient);
+    TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData,
+               kSingleClient);
 
     // Test with multiple client
-    runShellTest(config, uidMap, pullerManager, pushedList, expectedData, kNumClients);
+    TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData, kNumClients);
 }
 
 TEST(ShellSubscriberTest, testPulledSubscription) {
@@ -218,7 +654,7 @@
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
     const vector<int32_t> uids = {AID_SYSTEM};
-    EXPECT_CALL(*pullerManager, Pull(10016, uids, _, _))
+    EXPECT_CALL(*pullerManager, Pull(CPU_ACTIVE_TIME, uids, _, _))
             .WillRepeatedly(Invoke([](int tagId, const vector<int32_t>&, const int64_t,
                                       vector<std::shared_ptr<LogEvent>>* data) {
                 data->clear();
@@ -228,12 +664,12 @@
             }));
 
     // Test with single client
-    runShellTest(getPulledConfig(), uidMap, pullerManager, {}, {getExpectedPulledData()},
-                 kSingleClient);
+    TRACE_CALL(runShellTest, getPulledConfig(), uidMap, pullerManager, /*pushedEvents=*/{},
+               {getExpectedPulledData()}, kSingleClient);
 
     // Test with multiple clients.
-    runShellTest(getPulledConfig(), uidMap, pullerManager, {}, {getExpectedPulledData()},
-                 kNumClients);
+    TRACE_CALL(runShellTest, getPulledConfig(), uidMap, pullerManager, {},
+               {getExpectedPulledData()}, kNumClients);
 }
 
 TEST(ShellSubscriberTest, testBothSubscriptions) {
@@ -241,7 +677,7 @@
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
 
     const vector<int32_t> uids = {AID_SYSTEM};
-    EXPECT_CALL(*pullerManager, Pull(10016, uids, _, _))
+    EXPECT_CALL(*pullerManager, Pull(CPU_ACTIVE_TIME, uids, _, _))
             .WillRepeatedly(Invoke([](int tagId, const vector<int32_t>&, const int64_t,
                                       vector<std::shared_ptr<LogEvent>>* data) {
                 data->clear();
@@ -253,30 +689,34 @@
     vector<std::shared_ptr<LogEvent>> pushedList = getPushedEvents();
 
     ShellSubscription config = getPulledConfig();
-    config.add_pushed()->set_atom_id(29);
+    config.add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
 
     vector<ShellData> expectedData;
     ShellData shellData1;
     shellData1.add_atom()->mutable_screen_state_changed()->set_state(
             ::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    shellData1.add_elapsed_timestamp_nanos(pushedList[0]->GetElapsedTimestampNs());
     ShellData shellData2;
     shellData2.add_atom()->mutable_screen_state_changed()->set_state(
             ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+    shellData2.add_elapsed_timestamp_nanos(pushedList[1]->GetElapsedTimestampNs());
     expectedData.push_back(getExpectedPulledData());
     expectedData.push_back(shellData1);
     expectedData.push_back(shellData2);
 
     // Test with single client
-    runShellTest(config, uidMap, pullerManager, pushedList, expectedData, kSingleClient);
+    TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData,
+               kSingleClient);
 
     // Test with multiple client
-    runShellTest(config, uidMap, pullerManager, pushedList, expectedData, kNumClients);
+    TRACE_CALL(runShellTest, config, uidMap, pullerManager, pushedList, expectedData, kNumClients);
 }
 
 TEST(ShellSubscriberTest, testMaxSizeGuard) {
     sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    sp<ShellSubscriber> shellManager = new ShellSubscriber(uidMap, pullerManager);
+    sp<ShellSubscriber> shellManager =
+            new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
 
     // set up 2 pipes for read/write config and data
     int fds_config[2];
@@ -299,11 +739,12 @@
 TEST(ShellSubscriberTest, testMaxSubscriptionsGuard) {
     sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    sp<ShellSubscriber> shellManager = new ShellSubscriber(uidMap, pullerManager);
+    sp<ShellSubscriber> shellManager =
+            new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
 
     // create a simple config to get screen events
     ShellSubscription config;
-    config.add_pushed()->set_atom_id(29);
+    config.add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
 
     size_t bufferSize = config.ByteSize();
     vector<uint8_t> buffer(bufferSize);
@@ -348,15 +789,16 @@
 TEST(ShellSubscriberTest, testDifferentConfigs) {
     sp<MockUidMap> uidMap = new NaggyMock<MockUidMap>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
-    sp<ShellSubscriber> shellManager = new ShellSubscriber(uidMap, pullerManager);
+    sp<ShellSubscriber> shellManager =
+            new ShellSubscriber(uidMap, pullerManager, /*LogEventFilter=*/nullptr);
 
     // number of different configs
     int numConfigs = 2;
 
     // create a simple config to get screen events
     ShellSubscription configs[numConfigs];
-    configs[0].add_pushed()->set_atom_id(29);
-    configs[1].add_pushed()->set_atom_id(32);
+    configs[0].add_pushed()->set_atom_id(SCREEN_STATE_CHANGED);
+    configs[1].add_pushed()->set_atom_id(PLUGGED_STATE_CHANGED);
 
     vector<vector<uint8_t>> configBuffers;
     for (int i = 0; i < numConfigs; i++) {
@@ -387,43 +829,70 @@
     }
 
     // send a log event that matches the config.
-    for (const auto& event : getPushedEvents()) {
+    vector<std::shared_ptr<LogEvent>> pushedList = getPushedEvents();
+    for (const auto& event : pushedList) {
         shellManager->onLogEvent(*event);
     }
 
     // Validate Config 1
-    string receivedData = readData(fds_datas[0][0]);
+    ShellData actual1 = readData(fds_datas[0][0]);
     ShellData expected1;
     expected1.add_atom()->mutable_screen_state_changed()->set_state(
             ::android::view::DisplayStateEnum::DISPLAY_STATE_ON);
-    EXPECT_EQ(receivedData, expected1.SerializeAsString());
+    expected1.add_elapsed_timestamp_nanos(pushedList[0]->GetElapsedTimestampNs());
+    EXPECT_THAT(expected1, EqShellData(actual1));
 
-    receivedData = readData(fds_datas[0][0]);
+    ShellData actual2 = readData(fds_datas[0][0]);
     ShellData expected2;
     expected2.add_atom()->mutable_screen_state_changed()->set_state(
             ::android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
-    EXPECT_EQ(receivedData, expected2.SerializeAsString());
+    expected2.add_elapsed_timestamp_nanos(pushedList[1]->GetElapsedTimestampNs());
+    EXPECT_THAT(expected2, EqShellData(actual2));
 
     // Validate Config 2, repeating the process
-    receivedData = readData(fds_datas[1][0]);
+    ShellData actual3 = readData(fds_datas[1][0]);
     ShellData expected3;
     expected3.add_atom()->mutable_plugged_state_changed()->set_state(
             BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
-    EXPECT_EQ(receivedData, expected3.SerializeAsString());
+    expected3.add_elapsed_timestamp_nanos(pushedList[2]->GetElapsedTimestampNs());
+    EXPECT_THAT(expected3, EqShellData(actual3));
 
-    receivedData = readData(fds_datas[1][0]);
+    ShellData actual4 = readData(fds_datas[1][0]);
     ShellData expected4;
     expected4.add_atom()->mutable_plugged_state_changed()->set_state(
             BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
-    EXPECT_EQ(receivedData, expected4.SerializeAsString());
+    expected4.add_elapsed_timestamp_nanos(pushedList[3]->GetElapsedTimestampNs());
+    EXPECT_THAT(expected4, EqShellData(actual4));
 
     // 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
 
 }  // namespace statsd
 }  // namespace os
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/statsd/tests/state/StateTracker_test.cpp b/statsd/tests/state/StateTracker_test.cpp
index 6516c15..d2fbb3f 100644
--- a/statsd/tests/state/StateTracker_test.cpp
+++ b/statsd/tests/state/StateTracker_test.cpp
@@ -113,6 +113,7 @@
     mgr.registerListener(util::SCREEN_STATE_CHANGED, listener1);
     EXPECT_EQ(1, mgr.getStateTrackersCount());
     EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    mgr.unregisterListener(util::SCREEN_STATE_CHANGED, listener1);
 }
 
 TEST(StateManagerTest, TestOnLogEvent) {
diff --git a/statsd/tests/statsd_test_util.cpp b/statsd/tests/statsd_test_util.cpp
index b4a65e9..db21817 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"
 
@@ -34,11 +33,11 @@
 namespace os {
 namespace statsd {
 
-void StatsServiceConfigTest::sendConfig(const StatsdConfig& config) {
+bool StatsServiceConfigTest::sendConfig(const StatsdConfig& config) {
     string str;
     config.SerializeToString(&str);
     std::vector<uint8_t> configAsVec(str.begin(), str.end());
-    service->addConfiguration(kConfigKey, configAsVec, kCallingUid);
+    return service->addConfiguration(kConfigKey, configAsVec, kCallingUid).isOk();
 }
 
 ConfigMetricsReport StatsServiceConfigTest::getReports(sp<StatsLogProcessor> processor,
@@ -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,8 +978,8 @@
     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());
@@ -1112,14 +1111,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());
@@ -1258,11 +1258,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 +1280,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 +1310,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,10 +1361,46 @@
     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();
+    AStatsEvent_setAtomId(statsEvent, util::PHONE_SIGNAL_STRENGTH_CHANGED);
+    AStatsEvent_addBoolAnnotation(statsEvent, ASTATSLOG_ANNOTATION_ID_TRUNCATE_TIMESTAMP, true);
+    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;
+}
+
 sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
                                               const StatsdConfig& config, const ConfigKey& key,
                                               const shared_ptr<IPullAtomCallback>& puller,
-                                              const int32_t atomTag, const sp<UidMap> uidMap) {
+                                              const int32_t atomTag, const sp<UidMap> uidMap,
+                                              const shared_ptr<LogEventFilter>& logEventFilter) {
     sp<StatsPullerManager> pullerManager = new StatsPullerManager();
     if (puller != nullptr) {
         pullerManager->RegisterPullAtomCallback(/*uid=*/0, atomTag, NS_PER_SEC, NS_PER_SEC * 10, {},
@@ -1377,14 +1414,45 @@
         new AlarmMonitor(1,
                          [](const shared_ptr<IStatsCompanionService>&, int64_t){},
                          [](const shared_ptr<IStatsCompanionService>&){});
-    sp<StatsLogProcessor> processor =
-            new StatsLogProcessor(uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
-                                  timeBaseNs, [](const ConfigKey&) { return true; },
-                                  [](const int&, const vector<int64_t>&) {return true;});
+    sp<StatsLogProcessor> processor = new StatsLogProcessor(
+            uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs,
+            [](const ConfigKey&) { return true; },
+            [](const int&, const vector<int64_t>&) { return true; },
+            [](const ConfigKey&, const string&, const vector<int64_t>&) {}, logEventFilter);
+
     processor->OnConfigUpdated(currentTimeNs, key, config);
     return processor;
 }
 
+LogEventFilter::AtomIdSet CreateAtomIdSetDefault() {
+    LogEventFilter::AtomIdSet resultList(std::move(StatsLogProcessor::getDefaultAtomIdSet()));
+    StateManager::getInstance().addAllAtomIds(resultList);
+    return resultList;
+}
+
+LogEventFilter::AtomIdSet CreateAtomIdSetFromConfig(const StatsdConfig& config) {
+    LogEventFilter::AtomIdSet resultList(std::move(StatsLogProcessor::getDefaultAtomIdSet()));
+
+    // Parse the config for atom ids. A combination atom matcher is a combination of (in the end)
+    // simple atom matchers. So by adding all the atoms from the simple atom matchers
+    // function adds all of the atoms.
+    for (int i = 0; i < config.atom_matcher_size(); i++) {
+        const AtomMatcher& matcher = config.atom_matcher(i);
+        if (matcher.has_simple_atom_matcher()) {
+            EXPECT_TRUE(matcher.simple_atom_matcher().has_atom_id());
+            resultList.insert(matcher.simple_atom_matcher().atom_id());
+        }
+    }
+
+    for (int i = 0; i < config.state_size(); i++) {
+        const State& state = config.state(i);
+        EXPECT_TRUE(state.has_atom_id());
+        resultList.insert(state.atom_id());
+    }
+
+    return resultList;
+}
+
 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) {
@@ -2108,6 +2176,46 @@
     return pulledAtomStats;
 }
 
+void createStatsEvent(AStatsEvent* statsEvent, uint8_t typeId, uint32_t atomId) {
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    fillStatsEventWithSampleValue(statsEvent, typeId);
+}
+
+void fillStatsEventWithSampleValue(AStatsEvent* statsEvent, uint8_t typeId) {
+    int int32Array[2] = {3, 6};
+    uint32_t uids[] = {1001, 1002};
+    const char* tags[] = {"tag1", "tag2"};
+
+    switch (typeId) {
+        case INT32_TYPE:
+            AStatsEvent_writeInt32(statsEvent, 10);
+            break;
+        case INT64_TYPE:
+            AStatsEvent_writeInt64(statsEvent, 1000L);
+            break;
+        case STRING_TYPE:
+            AStatsEvent_writeString(statsEvent, "test");
+            break;
+        case LIST_TYPE:
+            AStatsEvent_writeInt32Array(statsEvent, int32Array, 2);
+            break;
+        case FLOAT_TYPE:
+            AStatsEvent_writeFloat(statsEvent, 1.3f);
+            break;
+        case BOOL_TYPE:
+            AStatsEvent_writeBool(statsEvent, 1);
+            break;
+        case BYTE_ARRAY_TYPE:
+            AStatsEvent_writeByteArray(statsEvent, (uint8_t*)"test", strlen("test"));
+            break;
+        case ATTRIBUTION_CHAIN_TYPE:
+            AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
+            break;
+        default:
+            break;
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/statsd_test_util.h b/statsd/tests/statsd_test_util.h
index b72dfe4..cba3ebf 100644
--- a/statsd/tests/statsd_test_util.h
+++ b/statsd/tests/statsd_test_util.h
@@ -16,10 +16,12 @@
 
 #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>
+#include <aidl/android/os/StatsSubscriptionCallbackReason.h>
 #include <gmock/gmock.h>
-#include <google/protobuf/util/message_differencer.h>
 #include <gtest/gtest.h>
 
 #include "src/StatsLogProcessor.h"
@@ -32,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"
 
@@ -41,11 +44,13 @@
 
 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;
+using ::aidl::android::os::StatsSubscriptionCallbackReason;
 using android::util::ProtoReader;
 using google::protobuf::RepeatedPtrField;
-using google::protobuf::util::MessageDifferencer;
 using Status = ::ndk::ScopedAStatus;
 using PackageInfoSnapshot = UidMapping_PackageInfoSnapshot;
 using PackageInfo = UidMapping_PackageInfoSnapshot_PackageInfo;
@@ -81,23 +86,49 @@
     MOCK_METHOD(std::set<int32_t>, getAppUid, (const string& package), (const));
 };
 
+class BasicMockLogEventFilter : public LogEventFilter {
+public:
+    MOCK_METHOD(void, setFilteringEnabled, (bool isEnabled), (override));
+    MOCK_METHOD(void, setAtomIds, (AtomIdSet tagIds, ConsumerId consumer), (override));
+};
+
 class MockPendingIntentRef : public aidl::android::os::BnPendingIntentRef {
 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,
                         const StatsDimensionsValueParcel& dimensionsValueParcel));
 };
 
+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,
+                (StatsSubscriptionCallbackReason in_reason,
+                 const std::vector<uint8_t>& in_subscriptionPayload),
+                (override));
+};
+
 class StatsServiceConfigTest : public ::testing::Test {
 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 = SharedRefBase::make<StatsService>(new UidMap(), /* queue */ nullptr);
+        service = createStatsService();
         // Removing config file from data/misc/stats-service and data/misc/stats-data if present
         ConfigKey configKey(kCallingUid, kConfigKey);
         service->removeConfiguration(kConfigKey, kCallingUid);
@@ -115,7 +146,12 @@
                                           ADB_DUMP, NO_TIME_CONSTRAINTS, nullptr);
     }
 
-    void sendConfig(const StatsdConfig& config);
+    virtual shared_ptr<StatsService> createStatsService() {
+        return SharedRefBase::make<StatsService>(new UidMap(), /* queue */ nullptr,
+                                                 /* LogEventFilter */ nullptr);
+    }
+
+    bool sendConfig(const StatsdConfig& config);
 
     ConfigMetricsReport getReports(sp<StatsLogProcessor> processor, int64_t timestamp,
                                    bool include_current = false);
@@ -502,6 +538,12 @@
         const vector<string>& repeatedStringField, const bool* repeatedBoolField,
         const size_t repeatedBoolFieldLength, const vector<int>& repeatedEnumField);
 
+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,
@@ -512,12 +554,19 @@
         const bool* repeatedBoolField, const size_t repeatedBoolFieldLength,
         const vector<int>& repeatedEnumField);
 
+void createStatsEvent(AStatsEvent* statsEvent, uint8_t typeId, uint32_t atomId);
+
+void fillStatsEventWithSampleValue(AStatsEvent* statsEvent, uint8_t typeId);
+
 // 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 ConfigKey& key,
-                                              const shared_ptr<IPullAtomCallback>& puller = nullptr,
-                                              const int32_t atomTag = 0 /*for puller only*/,
-                                              const sp<UidMap> = new UidMap());
+sp<StatsLogProcessor> CreateStatsLogProcessor(
+        const int64_t timeBaseNs, const 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);
+
+LogEventFilter::AtomIdSet CreateAtomIdSetDefault();
+LogEventFilter::AtomIdSet CreateAtomIdSetFromConfig(const StatsdConfig& config);
 
 // Util function to sort the log events by timestamp.
 void sortLogEventsByTimestamp(std::vector<std::unique_ptr<LogEvent>> *events);
@@ -749,17 +798,6 @@
         const std::vector<std::vector<uint8_t>>& certHashes, const std::vector<bool>& deleted,
         const std::vector<uint32_t>& installerIndices, const bool hashStrings);
 
-// Checks equality on explicitly set values.
-MATCHER(ProtoEq, "") {
-    return MessageDifferencer::Equals(std::get<0>(arg), std::get<1>(arg));
-}
-
-// Checks equality on explicitly and implicitly set values.
-// Implicitly set values comes from fields with a default value specifier.
-MATCHER(ProtoEquiv, "") {
-    return MessageDifferencer::Equivalent(std::get<0>(arg), std::get<1>(arg));
-}
-
 template <typename T>
 std::vector<T> concatenate(const vector<T>& a, const vector<T>& b) {
     vector<T> result(a);
@@ -768,6 +806,14 @@
 }
 
 StatsdStatsReport_PulledAtomStats getPulledAtomStats(int atom_id);
+
+template <typename P>
+std::vector<uint8_t> protoToBytes(const P& proto) {
+    const size_t byteSize = proto.ByteSizeLong();
+    vector<uint8_t> bytes(byteSize);
+    proto.SerializeToArray(bytes.data(), byteSize);
+    return bytes;
+}
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/statsd_test_util_test.cpp b/statsd/tests/statsd_test_util_test.cpp
index 9b28446..3db5ec3 100644
--- a/statsd/tests/statsd_test_util_test.cpp
+++ b/statsd/tests/statsd_test_util_test.cpp
@@ -14,6 +14,7 @@
 
 #include "statsd_test_util.h"
 
+#include "gtest_matchers.h"
 #include "src/stats_log.pb.h"
 
 namespace android {
@@ -160,7 +161,7 @@
             appName, uid, version, versionString, /* installer */ nullopt, /* certHash */ {},
             /* deleted */ false, hashStrings, /* installerIndex */ nullopt);
 
-    EXPECT_THAT(packageInfos, Pointwise(ProtoEq(), {packageInfo}));
+    EXPECT_THAT(packageInfos, Pointwise(EqPackageInfo(), {packageInfo}));
 }
 
 TEST_P(StatsdTestUtil_PackageInfo_HashStrings, TestBuildPackageInfosNonEmptyOptionalParams) {
@@ -173,7 +174,7 @@
             buildPackageInfo(appName, uid, version, versionString, installer, /* certHash */ {'a'},
                              /* deleted */ false, hashStrings, /* installerIndex */ 3);
 
-    EXPECT_THAT(packageInfos, Pointwise(ProtoEq(), {packageInfo}));
+    EXPECT_THAT(packageInfos, Pointwise(EqPackageInfo(), {packageInfo}));
 }
 
 #else
diff --git a/statsd/tests/storage/StorageManager_test.cpp b/statsd/tests/storage/StorageManager_test.cpp
index 96409e6..db80e77 100644
--- a/statsd/tests/storage/StorageManager_test.cpp
+++ b/statsd/tests/storage/StorageManager_test.cpp
@@ -15,12 +15,15 @@
 #include "src/storage/StorageManager.h"
 
 #include <android-base/unique_fd.h>
+#include <android-modules-utils/sdk_level.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
 
 #include "android-base/stringprintf.h"
 #include "stats_log_util.h"
+#include "tests/statsd_test_util.h"
+#include "utils/DbUtils.h"
 
 #ifdef __ANDROID__
 
@@ -29,6 +32,7 @@
 namespace statsd {
 
 using namespace testing;
+using android::modules::sdklevel::IsAtLeastU;
 using std::make_shared;
 using std::shared_ptr;
 using std::vector;
@@ -278,6 +282,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..8a9b337
--- /dev/null
+++ b/statsd/tests/utils/DbUtils_test.cpp
@@ -0,0 +1,435 @@
+/*
+ * 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 <android-modules-utils/sdk_level.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 android::modules::sdklevel::IsAtLeastU;
+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..d452402 100644
--- a/statsd/tools/localtools/Android.bp
+++ b/statsd/tools/localtools/Android.bp
@@ -7,6 +7,7 @@
     manifest: "localdrive_manifest.txt",
     srcs: [
         "src/com/android/statsd/shelltools/localdrive/*.java",
+        "src/com/android/statsd/shelltools/CustomExtensionRegistry.java",
         "src/com/android/statsd/shelltools/Utils.java",
     ],
     static_libs: [
@@ -19,6 +20,7 @@
     name: "statsd_testdrive_lib",
     srcs: [
         "src/com/android/statsd/shelltools/testdrive/*.java",
+        "src/com/android/statsd/shelltools/CustomExtensionRegistry.java",
         "src/com/android/statsd/shelltools/Utils.java",
     ],
     static_libs: [
diff --git a/statsd/tools/localtools/src/com/android/statsd/shelltools/CustomExtensionRegistry.java b/statsd/tools/localtools/src/com/android/statsd/shelltools/CustomExtensionRegistry.java
new file mode 100644
index 0000000..def616c
--- /dev/null
+++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/CustomExtensionRegistry.java
@@ -0,0 +1,136 @@
+/*
+ * 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.automotive.caruilib.AutomotiveCaruilibAtoms;
+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.google.protobuf.ExtensionRegistry;
+
+/**
+ * CustomExtensionRegistry for local use of statsd.
+ */
+public class CustomExtensionRegistry {
+
+    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);
+        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);
+    }
+}
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..3da7222 100644
--- a/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
+++ b/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
@@ -21,6 +21,7 @@
 import com.android.os.StatsLog.StatsLogReport;
 
 import com.google.common.io.Files;
+import com.google.protobuf.ExtensionRegistry;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -40,6 +41,8 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import com.android.statsd.shelltools.CustomExtensionRegistry;
+
 /**
  * Utilities for local use of statsd.
  */
@@ -112,7 +115,8 @@
                     "--include_current_bucket",
                     "--proto");
             ConfigMetricsReportList reportList =
-                    ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile));
+                    ConfigMetricsReportList.parseFrom(new FileInputStream(outputFile),
+                    CustomExtensionRegistry.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 2e2be66..5be447c 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;
@@ -95,6 +96,8 @@
             "AID_NFC",
             "AID_SECURE_ELEMENT",
             "com.google.android.wearable.media.routing",
+            "com.google.android.healthconnect.controller",
+            "com.android.telephony.qns",
     };
     private static final String[] DEFAULT_PULL_SOURCES = {
             "AID_KEYSTORE", "AID_RADIO", "AID_SYSTEM",
@@ -594,6 +597,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/AndroidTest.xml b/tests/AndroidTest.xml
index be8a12a..e33c2a0 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -28,4 +28,12 @@
     <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>
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- avoid restarting device after modifying settings -->
+        <option name="force-skip-system-props" value="true" />
+
+        <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+        <option name="restore-settings" value="true" />
+    </target_preparer>
 </configuration>
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/src/android/cts/statsd/atom/AtomTestCase.java b/tests/src/android/cts/statsd/atom/AtomTestCase.java
index fc37543..0e6c9c6 100644
--- a/tests/src/android/cts/statsd/atom/AtomTestCase.java
+++ b/tests/src/android/cts/statsd/atom/AtomTestCase.java
@@ -304,14 +304,16 @@
     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);
+        try {
+            Files.write(config.toByteArray(), configFile);
+            String remotePath = "/data/local/tmp/" + configFile.getName();
+            getDevice().pushFile(configFile, remotePath);
+            getDevice().executeShellCommand(String.join(" ", "cat", remotePath, "|",
+                    UPDATE_CONFIG_CMD, String.valueOf(SHELL_UID), String.valueOf(CONFIG_ID)));
+            getDevice().executeShellCommand("rm " + remotePath);
+        } finally {
+            configFile.delete();
+        }
     }
 
     protected void removeConfig(long configId) throws Exception {
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..5191f39
--- /dev/null
+++ b/tests/src/android/cts/statsd/restricted/ReadRestrictedStatsPermissionTest.java
@@ -0,0 +1,14 @@
+package android.cts.statsd.restricted;
+
+import android.cts.statsd.atom.DeviceAtomTestCase;
+
+/**
+ * Tests Suite for restricted stats permissions.
+ */
+public class ReadRestrictedStatsPermissionTest extends DeviceAtomTestCase {
+
+    public void testReadRestrictedStatsPermission() throws Exception {
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE,
+                ".RestrictedPermissionTests", "testReadRestrictedStatsPermission");
+    }
+}