Add audit logging API

Bug: 295324350
Test: atest SecurityLoggingTest
Change-Id: Ie4abb9a5995930a5b049db5a04783a8531408729
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 340a79d..409b7b7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -195,6 +195,7 @@
     field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS";
     field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
     field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS";
+    field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING";
     field @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") public static final String MANAGE_DEVICE_POLICY_THEFT_DETECTION = "android.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION";
     field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES";
     field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS";
@@ -1288,6 +1289,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyDrawableResource> CREATOR;
   }
 
+  public final class DevicePolicyIdentifiers {
+    field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String AUDIT_LOGGING_POLICY = "auditLogging";
+  }
+
   public class DevicePolicyKeyguardService extends android.app.Service {
     ctor public DevicePolicyKeyguardService();
     method @Nullable public void dismiss();
@@ -1316,6 +1321,7 @@
     method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
+    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled();
     method public boolean isDeviceManaged();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
@@ -1331,6 +1337,8 @@
     method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
+    method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
     method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index a884ab0..3c56aaf 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -20,6 +20,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.admin.flags.Flags;
 import android.os.UserManager;
@@ -53,6 +54,15 @@
     public static final String SECURITY_LOGGING_POLICY = "securityLogging";
 
     /**
+     * String identifier for {@link DevicePolicyManager#setAuditLogEnabled}.
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
+    @SystemApi
+    public static final String AUDIT_LOGGING_POLICY = "auditLogging";
+
+    /**
      * String identifier for {@link DevicePolicyManager#setLockTaskPackages}.
      */
     public static final String LOCK_TASK_POLICY = "lockTask";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 367ade0..a6fda9d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -55,6 +55,7 @@
 import static android.Manifest.permission.SET_TIME_ZONE;
 import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
 import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
+import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
 import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
 import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
 import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
@@ -233,7 +234,6 @@
     private final boolean mParentInstance;
     private final DevicePolicyResourcesManager mResourcesManager;
 
-
     /** @hide */
     public DevicePolicyManager(Context context, IDevicePolicyManager service) {
         this(context, service, false);
@@ -14059,6 +14059,74 @@
     }
 
     /**
+     * Controls whether audit logging is enabled.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
+    @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+    public void setAuditLogEnabled(boolean enabled) {
+        throwIfParentInstance("setAuditLogEnabled");
+        try {
+            mService.setAuditLogEnabled(mContext.getPackageName(), true);
+        } catch (RemoteException re) {
+            re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @return Whether audit logging is enabled.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
+    @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+    public boolean isAuditLogEnabled() {
+        throwIfParentInstance("isAuditLogEnabled");
+        try {
+            return mService.isAuditLogEnabled(mContext.getPackageName());
+        } catch (RemoteException re) {
+            re.rethrowFromSystemServer();
+            // unreachable
+            return false;
+        }
+    }
+
+    /**
+     * Sets audit log event callback. Only one callback per UID is active at any time, when a new
+     * callback is set, the previous one is forgotten. Should only be called when audit log policy
+     * is enforced by the caller. Disabling the policy clears the callback. Each time a new callback
+     * is set, it will first be invoked with all the audit log events available at the time.
+     *
+     * @param callback callback to invoke when new audit log events become available or {@code null}
+     *                 to clear the callback.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
+    @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+    public void setAuditLogEventCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @Nullable Consumer<List<SecurityEvent>> callback) {
+        throwIfParentInstance("setAuditLogEventCallback");
+        final IAuditLogEventsCallback wrappedCallback = callback == null
+                ? null
+                : new IAuditLogEventsCallback.Stub() {
+                    @Override
+                    public void onNewAuditLogEvents(List<SecurityEvent> events) {
+                        executor.execute(() -> callback.accept(events));
+                    }
+                };
+        try {
+            mService.setAuditLogEventsCallback(mContext.getPackageName(), wrappedCallback);
+        } catch (RemoteException re) {
+            re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Called by device owner or profile owner of an organization-owned managed profile to retrieve
      * all new security logging entries since the last call to this API after device boots.
      *
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 07ee8de..1aee9fe 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -338,4 +338,9 @@
      * Enforces resolved security logging policy, should only be invoked from device policy engine.
      */
     public abstract void enforceSecurityLoggingPolicy(boolean enabled);
+
+    /**
+     * Enforces resolved audit logging policy, should only be invoked from device policy engine.
+     */
+    public abstract void enforceAuditLoggingPolicy(boolean enabled);
 }
diff --git a/core/java/android/app/admin/IAuditLogEventsCallback.aidl b/core/java/android/app/admin/IAuditLogEventsCallback.aidl
new file mode 100644
index 0000000..ab87117
--- /dev/null
+++ b/core/java/android/app/admin/IAuditLogEventsCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.app.admin.SecurityLog;
+
+/** @hide */
+oneway interface IAuditLogEventsCallback {
+    void onNewAuditLogEvents(in List<SecurityLog.SecurityEvent> events);
+}
\ No newline at end of file
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ca4a5ab..3a7a891c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -32,6 +32,7 @@
 import android.app.admin.PackagePolicy;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.FactoryResetProtectionPolicy;
+import android.app.admin.IAuditLogEventsCallback;
 import android.app.admin.ManagedProfileProvisioningParams;
 import android.app.admin.FullyManagedDeviceProvisioningParams;
 import android.app.admin.ManagedSubscriptionsPolicy;
@@ -441,6 +442,10 @@
     long forceNetworkLogs();
     long forceSecurityLogs();
 
+    void setAuditLogEnabled(String callerPackage, boolean enabled);
+    boolean isAuditLogEnabled(String callerPackage);
+    void setAuditLogEventsCallback(String callerPackage, in IAuditLogEventsCallback callback);
+
     boolean isUninstallInQueue(String packageName);
     void uninstallPackageWithActiveAdmins(String packageName);
 
diff --git a/core/java/android/app/admin/SecurityLog.aidl b/core/java/android/app/admin/SecurityLog.aidl
new file mode 100644
index 0000000..e5ae2df
--- /dev/null
+++ b/core/java/android/app/admin/SecurityLog.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+/** @hide */
+parcelable SecurityLog.SecurityEvent;
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4131644..71f06f1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3690,6 +3690,14 @@
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SECURITY_LOGGING"
                 android:protectionLevel="internal|role" />
 
+    <!-- Allows an application to use audit logging API.
+        @hide
+        @SystemApi
+        @FlaggedApi("android.app.admin.flags.security_log_v2_enabled")
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"
+        android:protectionLevel="internal|role" />
+
     <!-- Allows an application to set policy related to system updates.
         <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
         required to call APIs protected by this permission on users different to the calling user.
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 4305d91..53f2caf 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -771,6 +771,9 @@
     <!-- Permission required for CTS test - CtsDevicePolicyManagerTestCases -->
     <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" />
 
+    <!-- Permission required for CTS test - CtsDevicePolicyTestCases -->
+    <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" />
+
     <!-- Permission required for CTS test - CtsKeystoreTestCases -->
     <uses-permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION" />
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9b84f39..58e198e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -28,6 +28,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUTOFILL;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_BLUETOOTH;
@@ -332,6 +333,7 @@
 import android.app.admin.DeviceStateCache;
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.FullyManagedDeviceProvisioningParams;
+import android.app.admin.IAuditLogEventsCallback;
 import android.app.admin.IDevicePolicyManager;
 import android.app.admin.IntegerPolicyValue;
 import android.app.admin.IntentFilterPolicyKey;
@@ -2064,7 +2066,7 @@
         mLockPatternUtils = injector.newLockPatternUtils();
         mLockSettingsInternal = injector.getLockSettingsInternal();
         // TODO: why does SecurityLogMonitor need to be created even when mHasFeature == false?
-        mSecurityLogMonitor = new SecurityLogMonitor(this);
+        mSecurityLogMonitor = new SecurityLogMonitor(this, mHandler);
 
         mHasFeature = mInjector.hasFeature();
         mIsWatch = mInjector.getPackageManager()
@@ -2722,8 +2724,20 @@
     }
 
     private void maybeStartSecurityLogMonitorOnActivityManagerReady() {
-        synchronized (getLockObject()) {
-            if (mInjector.securityLogIsLoggingEnabled()) {
+        if (!mInjector.securityLogIsLoggingEnabled()) {
+            return;
+        }
+
+        if (securityLogV2Enabled()) {
+            boolean auditLoggingEnabled = Boolean.TRUE.equals(
+                    mDevicePolicyEngine.getResolvedPolicy(
+                            PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL));
+            boolean securityLoggingEnabled = Boolean.TRUE.equals(
+                    mDevicePolicyEngine.getResolvedPolicy(
+                            PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL));
+            setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
+        } else {
+            synchronized (getLockObject()) {
                 mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
                 mInjector.runCryptoSelfTest();
                 maybePauseDeviceWideLoggingLocked();
@@ -15784,7 +15798,22 @@
 
         @Override
         public void enforceSecurityLoggingPolicy(boolean enabled) {
-            enforceLoggingPolicy(enabled);
+            if (!securityLogV2Enabled()) {
+                return;
+            }
+            Boolean auditLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
+                    PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL);
+            enforceLoggingPolicy(enabled, Boolean.TRUE.equals(auditLoggingEnabled));
+        }
+
+        @Override
+        public void enforceAuditLoggingPolicy(boolean enabled) {
+            if (!securityLogV2Enabled()) {
+                return;
+            }
+            Boolean securityLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
+                    PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL);
+            enforceLoggingPolicy(Boolean.TRUE.equals(securityLoggingEnabled), enabled);
         }
 
         private List<EnforcingUser> getEnforcingUsers(Set<EnforcingAdmin> admins) {
@@ -15804,17 +15833,23 @@
         }
     }
 
-    private void enforceLoggingPolicy(boolean securityLoggingEnabled) {
-        Slogf.i(LOG_TAG, "Enforcing security logging, securityLoggingEnabled: %b",
-                securityLoggingEnabled);
-        SecurityLog.setLoggingEnabledProperty(securityLoggingEnabled);
-        if (securityLoggingEnabled) {
-            mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
+    private void enforceLoggingPolicy(
+            boolean securityLoggingEnabled, boolean auditLoggingEnabled) {
+        Slogf.i(LOG_TAG, "Enforcing logging policy, security: %b audit: %b",
+                securityLoggingEnabled, auditLoggingEnabled);
+        SecurityLog.setLoggingEnabledProperty(securityLoggingEnabled || auditLoggingEnabled);
+        setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
+    }
+
+    private void setLoggingConfiguration(
+            boolean securityLoggingEnabled, boolean auditLoggingEnabled) {
+        final int loggingEnabledUser = getSecurityLoggingEnabledUser();
+        mSecurityLogMonitor.setLoggingParams(
+                loggingEnabledUser, securityLoggingEnabled, auditLoggingEnabled);
+        if (securityLoggingEnabled || auditLoggingEnabled) {
             synchronized (getLockObject()) {
                 maybePauseDeviceWideLoggingLocked();
             }
-        } else {
-            mSecurityLogMonitor.stop();
         }
     }
 
@@ -17895,6 +17930,82 @@
     }
 
     @Override
+    public void setAuditLogEnabled(String callingPackage, boolean enabled) {
+        if (!mHasFeature) {
+            return;
+        }
+        final CallerIdentity caller = getCallerIdentity(callingPackage);
+
+        if (!securityLogV2Enabled()) {
+            throw new UnsupportedOperationException("Audit log not enabled");
+        }
+
+        EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
+                null /* admin */,
+                MANAGE_DEVICE_POLICY_AUDIT_LOGGING,
+                caller.getPackageName(),
+                caller.getUserId());
+        if (enabled) {
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.AUDIT_LOGGING,
+                    admin,
+                    new BooleanPolicyValue(true));
+        } else {
+            mDevicePolicyEngine.removeGlobalPolicy(
+                    PolicyDefinition.AUDIT_LOGGING,
+                    admin);
+            mSecurityLogMonitor.setAuditLogEventsCallback(caller.getUid(), null /* callback */);
+        }
+    }
+
+    @Override
+    public boolean isAuditLogEnabled(String callingPackage) {
+        if (!mHasFeature) {
+            return false;
+        }
+
+        if (!securityLogV2Enabled()) {
+            throw new UnsupportedOperationException("Audit log not enabled");
+        }
+
+        final CallerIdentity caller = getCallerIdentity(callingPackage);
+        EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
+                null /* admin */,
+                MANAGE_DEVICE_POLICY_AUDIT_LOGGING,
+                caller.getPackageName(),
+                caller.getUserId());
+
+        Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+                PolicyDefinition.AUDIT_LOGGING, admin);
+
+        return Boolean.TRUE.equals(policy);
+    }
+
+    @Override
+    public void setAuditLogEventsCallback(String callingPackage, IAuditLogEventsCallback callback) {
+        if (!mHasFeature) {
+            return;
+        }
+
+        final CallerIdentity caller = getCallerIdentity(callingPackage);
+        EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
+                null /* admin */,
+                MANAGE_DEVICE_POLICY_AUDIT_LOGGING,
+                caller.getPackageName(),
+                caller.getUserId());
+
+        Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+                PolicyDefinition.AUDIT_LOGGING, admin);
+
+        if (!Boolean.TRUE.equals(policy)) {
+            throw new IllegalStateException(
+                    "Managing app has to enable audit log before setting events callback");
+        }
+
+        mSecurityLogMonitor.setAuditLogEventsCallback(caller.getUid(), callback);
+    }
+
+    @Override
     public long forceSecurityLogs() {
         Preconditions.checkCallAuthorization(isAdb(getCallerIdentity())
                         || hasCallingOrSelfPermission(permission.FORCE_DEVICE_POLICY_MANAGER_LOGS),
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 3474db3..1247f9002 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -141,6 +141,13 @@
             PolicyEnforcerCallbacks::enforceSecurityLogging,
             new BooleanPolicySerializer());
 
+    static PolicyDefinition<Boolean> AUDIT_LOGGING = new PolicyDefinition<>(
+            new NoArgsPolicyKey(DevicePolicyIdentifiers.AUDIT_LOGGING_POLICY),
+            TRUE_MORE_RESTRICTIVE,
+            POLICY_FLAG_GLOBAL_ONLY_POLICY,
+            PolicyEnforcerCallbacks::enforceAuditLogging,
+            new BooleanPolicySerializer());
+
     static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>(
             new NoArgsPolicyKey(DevicePolicyIdentifiers.LOCK_TASK_POLICY),
             new TopPriority<>(List.of(
@@ -365,6 +372,8 @@
                 GENERIC_PERMISSION_GRANT);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.SECURITY_LOGGING_POLICY,
                 SECURITY_LOGGING);
+        POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUDIT_LOGGING_POLICY,
+                AUDIT_LOGGING);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.LOCK_TASK_POLICY, LOCK_TASK);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY,
                 USER_CONTROLLED_DISABLED_PACKAGES);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 4aaefa6..54242ab 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -136,6 +136,14 @@
         return true;
     }
 
+    static boolean enforceAuditLogging(
+            @Nullable Boolean value, @NonNull Context context, int userId,
+            @NonNull PolicyKey policyKey) {
+        final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
+        dpmi.enforceAuditLoggingPolicy(Boolean.TRUE.equals(value));
+        return true;
+    }
+
     static boolean setLockTask(
             @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
         List<String> packages = Collections.emptyList();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 7a4454b..02f3918 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -16,22 +16,32 @@
 
 package com.android.server.devicepolicy;
 
+import static android.app.admin.flags.Flags.securityLogV2Enabled;
+
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
 import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.IAuditLogEventsCallback;
 import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
+import android.os.Handler;
+import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
 
 import java.io.IOException;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -53,15 +63,11 @@
 
     private int mEnabledUser;
 
-    SecurityLogMonitor(DevicePolicyManagerService service) {
-        this(service, 0 /* id */);
-    }
-
-    @VisibleForTesting
-    SecurityLogMonitor(DevicePolicyManagerService service, long id) {
+    SecurityLogMonitor(DevicePolicyManagerService service, Handler handler) {
         mService = service;
-        mId = id;
+        mId = 0;
         mLastForceNanos = System.nanoTime();
+        mHandler = handler;
     }
 
     private static final boolean DEBUG = false;  // STOPSHIP if true.
@@ -118,6 +124,9 @@
     @GuardedBy("mLock")
     private boolean mCriticalLevelLogged = false;
 
+    private boolean mLegacyLogEnabled;
+    private boolean mAuditLogEnabled;
+
     /**
      * Last events fetched from log to check for overlap between batches. We can leave it empty if
      * we are sure there will be no overlap anymore, e.g. when we get empty batch.
@@ -143,6 +152,40 @@
     private long mLastForceNanos = 0;
 
     /**
+     * Handler shared with DPMS.
+     */
+    private final Handler mHandler;
+
+    /**
+     * Oldest events get purged from audit log buffer if total number exceeds this value.
+     */
+    private static final int MAX_AUDIT_LOG_EVENTS = 10000;
+    /**
+     * Events older than this get purged from audit log buffer.
+     */
+    private static final long MAX_AUDIT_LOG_EVENT_AGE_NS = TimeUnit.HOURS.toNanos(8);
+
+    /**
+     * Audit log callbacks keyed by UID. The code should maintain the following invariant: all
+     * callbacks in this map have received (or are scheduled to receive) all events in
+     * mAuditLogEventsBuffer. To ensure this, before a callback is put into this map, it must be
+     * scheduled to receive all the events in the buffer, and conversely, before a new chunk of
+     * events is added to the buffer, it must be scheduled to be sent to all callbacks already in
+     * this list. All scheduling should happen on mHandler, so that they aren't reordered, and
+     * while holding the lock. This ensures that no callback misses an event or receives a duplicate
+     * or out of order events.
+     */
+    @GuardedBy("mLock")
+    private final SparseArray<IAuditLogEventsCallback> mAuditLogCallbacks = new SparseArray<>();
+
+    /**
+     * Audit log event buffer. It is shrunk automatically whenever either there are too many events
+     * or the oldest one is too old.
+     */
+    @GuardedBy("mLock")
+    private final ArrayDeque<SecurityEvent> mAuditLogEventBuffer = new ArrayDeque<>();
+
+    /**
      * Start security logging.
      *
      * @param enabledUser which user logging is enabled on, or USER_ALL to enable logging for all
@@ -154,18 +197,8 @@
         mLock.lock();
         try {
             if (mMonitorThread == null) {
-                mPendingLogs = new ArrayList<>();
-                mCriticalLevelLogged = false;
-                mId = 0;
-                mAllowedToRetrieve = false;
-                mNextAllowedRetrievalTimeMillis = -1;
-                mPaused = false;
-
-                mMonitorThread = new Thread(this);
-                mMonitorThread.start();
-
-                SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED);
-                Slog.i(TAG, "Security log monitor thread started");
+                resetLegacyBufferLocked();
+                startMonitorThreadLocked();
             } else {
                 Slog.i(TAG, "Security log monitor thread is already running");
             }
@@ -176,29 +209,82 @@
 
     void stop() {
         Slog.i(TAG, "Stopping security logging.");
-        SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STOPPED);
         mLock.lock();
         try {
             if (mMonitorThread != null) {
-                mMonitorThread.interrupt();
-                try {
-                    mMonitorThread.join(TimeUnit.SECONDS.toMillis(5));
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "Interrupted while waiting for thread to stop", e);
-                }
-                // Reset state and clear buffer
-                mPendingLogs = new ArrayList<>();
-                mId = 0;
-                mAllowedToRetrieve = false;
-                mNextAllowedRetrievalTimeMillis = -1;
-                mPaused = false;
-                mMonitorThread = null;
+                stopMonitorThreadLocked();
+                resetLegacyBufferLocked();
             }
         } finally {
             mLock.unlock();
         }
     }
 
+    void setLoggingParams(int enabledUser, boolean legacyLogEnabled, boolean auditLogEnabled) {
+        Slogf.i(TAG, "Setting logging params, user = %d -> %d, legacy: %b -> %b, audit %b -> %b",
+                mEnabledUser, enabledUser, mLegacyLogEnabled, legacyLogEnabled, mAuditLogEnabled,
+                auditLogEnabled);
+        mLock.lock();
+        try {
+            mEnabledUser = enabledUser;
+            if (mMonitorThread == null && (legacyLogEnabled || auditLogEnabled)) {
+                startMonitorThreadLocked();
+            } else if (mMonitorThread != null && !legacyLogEnabled && !auditLogEnabled) {
+                stopMonitorThreadLocked();
+            }
+
+            if (mLegacyLogEnabled != legacyLogEnabled) {
+                resetLegacyBufferLocked();
+                mLegacyLogEnabled = legacyLogEnabled;
+            }
+
+            if (mAuditLogEnabled != auditLogEnabled) {
+                resetAuditBufferLocked();
+                mAuditLogEnabled = auditLogEnabled;
+            }
+        } finally {
+            mLock.unlock();
+        }
+
+    }
+
+    @GuardedBy("mLock")
+    private void startMonitorThreadLocked() {
+        mId = 0;
+        mPaused = false;
+        mMonitorThread = new Thread(this);
+        mMonitorThread.start();
+        SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED);
+        Slog.i(TAG, "Security log monitor thread started");
+    }
+
+    @GuardedBy("mLock")
+    private void stopMonitorThreadLocked() {
+        mMonitorThread.interrupt();
+        try {
+            mMonitorThread.join(TimeUnit.SECONDS.toMillis(5));
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Interrupted while waiting for thread to stop", e);
+        }
+        mMonitorThread = null;
+        SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STOPPED);
+    }
+
+    @GuardedBy("mLock")
+    private void resetLegacyBufferLocked() {
+        mPendingLogs = new ArrayList<>();
+        mCriticalLevelLogged = false;
+        mAllowedToRetrieve = false;
+        mNextAllowedRetrievalTimeMillis = -1;
+        Slog.i(TAG, "Legacy buffer reset.");
+    }
+
+    @GuardedBy("mLock")
+    private void resetAuditBufferLocked() {
+        mAuditLogEventBuffer.clear();
+        mAuditLogCallbacks.clear();
+    }
+
     /**
      * If logs are being collected, keep collecting them but stop notifying the device owner that
      * new logs are available (since they cannot be retrieved).
@@ -338,8 +424,7 @@
      */
     @GuardedBy("mLock")
     private void mergeBatchLocked(final ArrayList<SecurityEvent> newLogs) {
-        // Reserve capacity so that copying doesn't occur.
-        mPendingLogs.ensureCapacity(mPendingLogs.size() + newLogs.size());
+        List<SecurityEvent> dedupedLogs = new ArrayList<>();
         // Run through the first events of the batch to check if there is an overlap with previous
         // batch and if so, skip overlapping events. Events are sorted by timestamp, so we can
         // compare it in linear time by advancing two pointers, one for each batch.
@@ -358,8 +443,7 @@
             if (lastNanos > currentNanos) {
                 // New event older than the last we've seen so far, must be due to reordering.
                 if (DEBUG) Slog.d(TAG, "New event in the overlap: " + currentNanos);
-                assignLogId(curEvent);
-                mPendingLogs.add(curEvent);
+                dedupedLogs.add(curEvent);
                 curPos++;
             } else if (lastNanos < currentNanos) {
                 if (DEBUG) Slog.d(TAG, "Event disappeared from the overlap: " + lastNanos);
@@ -371,8 +455,7 @@
                     if (DEBUG) Slog.d(TAG, "Skipped dup event with timestamp: " + lastNanos);
                 } else {
                     // Wow, what a coincidence, or probably the clock is too coarse.
-                    assignLogId(curEvent);
-                    mPendingLogs.add(curEvent);
+                    dedupedLogs.add(curEvent);
                     if (DEBUG) Slog.d(TAG, "Event timestamp collision: " + lastNanos);
                 }
                 lastPos++;
@@ -380,12 +463,23 @@
             }
         }
         // Assign an id to the new logs, after the overlap with mLastEvents.
-        List<SecurityEvent> idLogs = newLogs.subList(curPos, newLogs.size());
-        for (SecurityEvent event : idLogs) {
+        dedupedLogs.addAll(newLogs.subList(curPos, newLogs.size()));
+        for (SecurityEvent event : dedupedLogs) {
             assignLogId(event);
         }
+
+        if (!securityLogV2Enabled() || mLegacyLogEnabled) {
+            addToLegacyBuffer(dedupedLogs);
+        }
+
+        if (securityLogV2Enabled() && mAuditLogEnabled) {
+            addAuditLogEvents(dedupedLogs);
+        }
+    }
+
+    private void addToLegacyBuffer(List<SecurityEvent> dedupedLogs) {
         // Save the rest of the new batch.
-        mPendingLogs.addAll(idLogs);
+        mPendingLogs.addAll(dedupedLogs);
 
         checkCriticalLevel();
 
@@ -453,7 +547,10 @@
 
                 saveLastEvents(newLogs);
                 newLogs.clear();
-                notifyDeviceOwnerOrProfileOwnerIfNeeded(force);
+
+                if (!securityLogV2Enabled() || mLegacyLogEnabled) {
+                    notifyDeviceOwnerOrProfileOwnerIfNeeded(force);
+                }
             } catch (IOException e) {
                 Log.e(TAG, "Failed to read security log", e);
             } catch (InterruptedException e) {
@@ -532,4 +629,121 @@
             return 0;
         }
     }
+
+    public void setAuditLogEventsCallback(int uid, IAuditLogEventsCallback callback) {
+        mLock.lock();
+        try {
+            if (callback == null) {
+                mAuditLogCallbacks.remove(uid);
+                Slogf.i(TAG, "Cleared audit log callback for UID %d", uid);
+                return;
+            }
+            // Create a copy while holding the lock, so that that new events are not added
+            // resulting in duplicates.
+            final List<SecurityEvent> events = new ArrayList<>(mAuditLogEventBuffer);
+            scheduleSendAuditLogs(uid, callback, events);
+            mAuditLogCallbacks.append(uid, callback);
+        } finally {
+            mLock.unlock();
+        }
+        Slogf.i(TAG, "Set audit log callback for UID %d", uid);
+    }
+
+    private void addAuditLogEvents(List<SecurityEvent> events) {
+        mLock.lock();
+        try {
+            if (mPaused) {
+                // TODO: maybe we need to stash the logs in some temp buffer wile paused so that
+                // they can be accessed after affiliation is fixed.
+                return;
+            }
+            if (!events.isEmpty()) {
+                for (int i = 0; i < mAuditLogCallbacks.size(); i++) {
+                    final int uid = mAuditLogCallbacks.keyAt(i);
+                    scheduleSendAuditLogs(uid, mAuditLogCallbacks.valueAt(i), events);
+                }
+            }
+            if (DEBUG) {
+                Slogf.d(TAG, "Adding audit %d events to % already present in the buffer",
+                        events.size(), mAuditLogEventBuffer.size());
+            }
+            mAuditLogEventBuffer.addAll(events);
+            trimAuditLogBufferLocked();
+            if (DEBUG) {
+                Slogf.d(TAG, "Audit event buffer size after trimming: %d",
+                        mAuditLogEventBuffer.size());
+            }
+        } finally {
+            mLock.unlock();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void trimAuditLogBufferLocked() {
+        long nowNanos = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis());
+
+        final Iterator<SecurityEvent> iterator = mAuditLogEventBuffer.iterator();
+        while (iterator.hasNext()) {
+            final SecurityEvent event = iterator.next();
+            if (mAuditLogEventBuffer.size() <= MAX_AUDIT_LOG_EVENTS
+                    && nowNanos - event.getTimeNanos() <= MAX_AUDIT_LOG_EVENT_AGE_NS) {
+                break;
+            }
+
+            iterator.remove();
+        }
+    }
+
+    private void scheduleSendAuditLogs(
+            int uid, IAuditLogEventsCallback callback, List<SecurityEvent> events) {
+        if (DEBUG) {
+            Slogf.d(TAG, "Scheduling to send %d audit log events to UID %d", events.size(), uid);
+        }
+        mHandler.post(() -> sendAuditLogs(uid, callback, events));
+    }
+
+    private void sendAuditLogs(
+            int uid, IAuditLogEventsCallback callback, List<SecurityEvent> events) {
+        try {
+            final int size = events.size();
+            if (DEBUG) {
+                Slogf.d(TAG, "Sending %d audit log events to UID %d", size, uid);
+            }
+            callback.onNewAuditLogEvents(events);
+            if (DEBUG) {
+                Slogf.d(TAG, "Sent %d audit log events to UID %d", size, uid);
+            }
+        } catch (RemoteException e) {
+            Slogf.e(TAG, e, "Failed to invoke audit log callback for UID %d", uid);
+            removeAuditLogEventsCallbackIfDead(uid, callback);
+        }
+    }
+
+    private void removeAuditLogEventsCallbackIfDead(int uid, IAuditLogEventsCallback callback) {
+        final IBinder binder = callback.asBinder();
+        if (binder.isBinderAlive()) {
+            Slog.i(TAG, "Callback binder is still alive, not removing.");
+            return;
+        }
+
+        mLock.lock();
+        try {
+            int index = mAuditLogCallbacks.indexOfKey(uid);
+            if (index < 0) {
+                Slogf.i(TAG, "Callback not registered for UID %d, nothing to remove", uid);
+                return;
+            }
+
+            final IBinder storedBinder = mAuditLogCallbacks.valueAt(index).asBinder();
+            if (!storedBinder.equals(binder)) {
+                Slogf.i(TAG, "Callback is already replaced for UID %d, not removing", uid);
+                return;
+            }
+
+            Slogf.i(TAG, "Removing callback for UID %d", uid);
+            mAuditLogCallbacks.removeAt(index);
+        } finally {
+            mLock.unlock();
+        }
+    }
 }