Merge "Move VCN utilities from services.jar to framework.jar" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index eb769cea..1f10678 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6198,6 +6198,7 @@
   }
 
   public class KeyguardManager {
+    method @FlaggedApi("android.app.device_unlock_listener") @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void addDeviceLockedStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.DeviceLockedStateListener);
     method @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void addKeyguardLockedStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.KeyguardLockedStateListener);
     method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence);
     method @Deprecated @RequiresPermission(android.Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult);
@@ -6207,10 +6208,15 @@
     method public boolean isKeyguardLocked();
     method public boolean isKeyguardSecure();
     method @Deprecated public android.app.KeyguardManager.KeyguardLock newKeyguardLock(String);
+    method @FlaggedApi("android.app.device_unlock_listener") @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void removeDeviceLockedStateListener(@NonNull android.app.KeyguardManager.DeviceLockedStateListener);
     method @RequiresPermission(android.Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) public void removeKeyguardLockedStateListener(@NonNull android.app.KeyguardManager.KeyguardLockedStateListener);
     method public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable android.app.KeyguardManager.KeyguardDismissCallback);
   }
 
+  @FlaggedApi("android.app.device_unlock_listener") @java.lang.FunctionalInterface public static interface KeyguardManager.DeviceLockedStateListener {
+    method public void onDeviceLockedStateChanged(boolean);
+  }
+
   public abstract static class KeyguardManager.KeyguardDismissCallback {
     ctor public KeyguardManager.KeyguardDismissCallback();
     method public void onDismissCancelled();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 529e7f9..149e474 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -129,6 +129,7 @@
 
   public abstract class PackageManager {
     method @NonNull public String getSdkSandboxPackageName();
+    method @FlaggedApi("android.content.pm.cloud_compilation_pm") @NonNull public static android.content.pm.SigningInfo getVerifiedSigningInfo(@NonNull String, int) throws android.content.pm.SigningInfoException;
     method @RequiresPermission(android.Manifest.permission.MAKE_UID_VISIBLE) public void makeUidVisible(int, int);
     field public static final String EXTRA_VERIFICATION_ROOT_HASH = "android.content.pm.extra.VERIFICATION_ROOT_HASH";
     field public static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 67108864; // 0x4000000
@@ -139,6 +140,18 @@
     method @NonNull public String getPackageName();
   }
 
+  public final class SigningInfo implements android.os.Parcelable {
+    method @FlaggedApi("android.content.pm.cloud_compilation_pm") public boolean signersMatchExactly(@NonNull android.content.pm.SigningInfo);
+    field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_JAR = 1; // 0x1
+    field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V2 = 2; // 0x2
+    field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V3 = 3; // 0x3
+    field @FlaggedApi("android.content.pm.cloud_compilation_pm") public static final int VERSION_SIGNING_BLOCK_V4 = 4; // 0x4
+  }
+
+  @FlaggedApi("android.content.pm.cloud_compilation_pm") public class SigningInfoException extends java.lang.Exception {
+    method @FlaggedApi("android.content.pm.cloud_compilation_pm") public int getCode();
+  }
+
 }
 
 package android.hardware.usb {
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 62820ad..67f7bee 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -52,7 +53,9 @@
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.IDeviceLockedStateListener;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IKeyguardLockedStateListener;
 import com.android.internal.util.Preconditions;
@@ -253,6 +256,26 @@
     private final ArrayMap<KeyguardLockedStateListener, Executor>
             mKeyguardLockedStateListeners = new ArrayMap<>();
 
+    private final IDeviceLockedStateListener mIDeviceLockedStateListener =
+            new IDeviceLockedStateListener.Stub() {
+                @Override
+                public void onDeviceLockedStateChanged(boolean isDeviceLocked) {
+                    if (!Flags.deviceUnlockListener()) {
+                        return;
+                    }
+                    synchronized (mDeviceLockedStateListeners) {
+                        mDeviceLockedStateListeners.forEach((listener, executor) -> {
+                            executor.execute(
+                                    () -> listener.onDeviceLockedStateChanged(isDeviceLocked));
+                        });
+                    }
+                }
+            };
+
+    @GuardedBy("mDeviceLockedStateListeners")
+    private final ArrayMap<DeviceLockedStateListener, Executor>
+            mDeviceLockedStateListeners = new ArrayMap<>();
+
     /**
      * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics
      * if enrolled) for the current user of the device. The caller is expected to launch this
@@ -1370,4 +1393,77 @@
             }
         }
     }
+
+
+    /**
+     * Listener for device locked state changes.
+     */
+    @FunctionalInterface
+    @FlaggedApi(Flags.FLAG_DEVICE_UNLOCK_LISTENER)
+    public interface DeviceLockedStateListener {
+        /**
+         * Callback function that executes when the device locked state changes.
+         */
+        void onDeviceLockedStateChanged(boolean isDeviceLocked);
+    }
+
+
+    /**
+     * Registers a listener to execute when the device locked state changes.
+     *
+     * @param executor The {@link Executor} where the {@code listener} will be invoked
+     * @param listener The listener to add to receive device locked state changes.
+     *
+     * @see #isDeviceLocked()
+     * @see #removeDeviceLockedStateListener(DeviceLockedStateListener)
+     */
+    @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+    @FlaggedApi(Flags.FLAG_DEVICE_UNLOCK_LISTENER)
+    public void addDeviceLockedStateListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull DeviceLockedStateListener listener) {
+        if (!Flags.deviceUnlockListener()) {
+            return;
+        }
+
+        synchronized (mDeviceLockedStateListeners) {
+            mDeviceLockedStateListeners.put(listener, executor);
+            if (mDeviceLockedStateListeners.size() > 1) {
+                return;
+            }
+            try {
+                mTrustManager.registerDeviceLockedStateListener(mIDeviceLockedStateListener,
+                        mContext.getDeviceId());
+            } catch (RemoteException re) {
+                Log.d(TAG, "TrustManager service died", re);
+            }
+        }
+    }
+
+    /**
+     * Unregisters a listener that executes when the device locked state changes.
+     *
+     * @param listener The listener to remove.
+     *
+     * @see #isDeviceLocked()
+     * @see #addDeviceLockedStateListener(Executor, DeviceLockedStateListener)
+     */
+    @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+    @FlaggedApi(Flags.FLAG_DEVICE_UNLOCK_LISTENER)
+    public void removeDeviceLockedStateListener(@NonNull DeviceLockedStateListener listener) {
+        if (!Flags.deviceUnlockListener()) {
+            return;
+        }
+
+        synchronized (mDeviceLockedStateListeners) {
+            mDeviceLockedStateListeners.remove(listener);
+            if (!mDeviceLockedStateListeners.isEmpty()) {
+                return;
+            }
+            try {
+                mTrustManager.unregisterDeviceLockedStateListener(mIDeviceLockedStateListener);
+            } catch (RemoteException re) {
+                Log.d(TAG, "TrustManager service died", re);
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/keyguard.aconfig b/core/java/android/app/keyguard.aconfig
new file mode 100644
index 0000000..9cd1c15
--- /dev/null
+++ b/core/java/android/app/keyguard.aconfig
@@ -0,0 +1,10 @@
+package: "android.app"
+container: "system"
+
+flag {
+     namespace: "wallet_integration"
+     name: "device_unlock_listener"
+     is_exported: true
+     description: "Enable listener API for device unlock."
+     bug: "296195355"
+}
\ No newline at end of file
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index 740f593..7cef2ef 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -18,6 +18,7 @@
 
 import android.app.trust.ITrustListener;
 import android.hardware.biometrics.BiometricSourceType;
+import com.android.internal.policy.IDeviceLockedStateListener;
 
 /**
  * System private API to comunicate with trust service.
@@ -42,4 +43,8 @@
     void clearAllBiometricRecognized(in BiometricSourceType target, int unlockedUser);
     boolean isActiveUnlockRunning(int userId);
     boolean isInSignificantPlace();
+    @EnforcePermission("SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE")
+    void registerDeviceLockedStateListener(in IDeviceLockedStateListener listener, int deviceId);
+    @EnforcePermission("SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE")
+    void unregisterDeviceLockedStateListener(in IDeviceLockedStateListener listener);
 }
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 88d4d69..75acac4 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -31,6 +31,8 @@
 import android.os.RemoteException;
 import android.util.ArrayMap;
 
+import com.android.internal.policy.IDeviceLockedStateListener;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -259,6 +261,35 @@
     }
 
     /**
+     * Registers a listener for device lock state events.
+     *
+     * Requires the {@link android.Manifest.permission#SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE}
+     * permission.
+     */
+    public void registerDeviceLockedStateListener(final IDeviceLockedStateListener listener,
+            int deviceId) {
+        try {
+            mService.registerDeviceLockedStateListener(listener, deviceId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters a listener for device lock state events.
+     *
+     * Requires the {@link android.Manifest.permission#SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE}
+     * permission.
+     */
+    public void unregisterDeviceLockedStateListener(final IDeviceLockedStateListener listener) {
+        try {
+            mService.unregisterDeviceLockedStateListener(listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * @return whether {@param userId} has enabled and configured trust agents. Ignores short-term
      * unavailability of trust due to {@link LockPatternUtils.StrongAuthTracker}.
      */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0ed9c87..3971e98 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import static android.content.pm.SigningInfo.AppSigningSchemeVersion;
 import static android.media.audio.Flags.FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY;
 
 import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES;
@@ -59,6 +60,8 @@
 import android.content.IntentSender;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -94,6 +97,7 @@
 import android.telephony.ims.SipDelegateManager;
 import android.util.AndroidException;
 import android.util.Log;
+import android.util.apk.ApkSignatureVerifier;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.pm.parsing.PackageInfoCommonUtils;
@@ -11838,4 +11842,31 @@
         throw new UnsupportedOperationException(
                 "parseServiceMetadata not implemented in subclass");
     }
+
+    /**
+     * Verifies and returns the
+     * <a href="https://source.android.com/docs/security/features/apksigning">app signing</a>
+     * information of the file at the given path. This operation takes a few milliseconds.
+     *
+     * Unlike {@link #getPackageArchiveInfo(String, PackageInfoFlags)} with {@link
+     * #GET_SIGNING_CERTIFICATES}, this method does not require the file to be a package archive
+     * file.
+     *
+     * @throws SigningInfoException if the verification fails
+     *
+     * @hide
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static @NonNull SigningInfo getVerifiedSigningInfo(@NonNull String path,
+            @AppSigningSchemeVersion int minAppSigningSchemeVersion) throws SigningInfoException {
+        ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        ParseResult<SigningDetails> result =
+                ApkSignatureVerifier.verify(input, path, minAppSigningSchemeVersion);
+        if (result.isError()) {
+            throw new SigningInfoException(
+                    result.getErrorCode(), result.getErrorMessage(), result.getException());
+        }
+        return new SigningInfo(result.getResult());
+    }
 }
diff --git a/core/java/android/content/pm/SigningInfo.java b/core/java/android/content/pm/SigningInfo.java
index 23daaf2..e4fbd1f 100644
--- a/core/java/android/content/pm/SigningInfo.java
+++ b/core/java/android/content/pm/SigningInfo.java
@@ -16,14 +16,20 @@
 
 package android.content.pm;
 
+import static android.content.pm.SigningDetails.SignatureSchemeVersion;
+
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.content.pm.SigningDetails.SignatureSchemeVersion;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.security.PublicKey;
 import java.util.Collection;
 
@@ -31,6 +37,55 @@
  * Information pertaining to the signing certificates used to sign a package.
  */
 public final class SigningInfo implements Parcelable {
+    /**
+     * JAR signing (v1 scheme).
+     * See https://source.android.com/docs/security/features/apksigning#v1.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int VERSION_JAR = SignatureSchemeVersion.JAR;
+
+    /**
+     * APK signature scheme v2.
+     * See https://source.android.com/docs/security/features/apksigning/v2.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int VERSION_SIGNING_BLOCK_V2 = SignatureSchemeVersion.SIGNING_BLOCK_V2;
+
+    /**
+     * APK signature scheme v3.
+     * See https://source.android.com/docs/security/features/apksigning/v3.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int VERSION_SIGNING_BLOCK_V3 = SignatureSchemeVersion.SIGNING_BLOCK_V3;
+
+    /**
+     * APK signature scheme v4.
+     * See https://source.android.com/docs/security/features/apksigning/v4.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int VERSION_SIGNING_BLOCK_V4 = SignatureSchemeVersion.SIGNING_BLOCK_V4;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"VERSION_"}, value = {
+            VERSION_JAR,
+            VERSION_SIGNING_BLOCK_V2,
+            VERSION_SIGNING_BLOCK_V3,
+            VERSION_SIGNING_BLOCK_V4,
+    })
+    public @interface AppSigningSchemeVersion {}
 
     @NonNull
     private final SigningDetails mSigningDetails;
@@ -198,6 +253,17 @@
         return mSigningDetails;
     }
 
+    /**
+     * Returns true if the signing certificates in this and other match exactly.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public boolean signersMatchExactly(@NonNull SigningInfo other) {
+        return mSigningDetails.signaturesMatchExactly(other.mSigningDetails);
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<SigningInfo> CREATOR =
             new Parcelable.Creator<SigningInfo>() {
         @Override
diff --git a/core/java/android/content/pm/SigningInfoException.java b/core/java/android/content/pm/SigningInfoException.java
new file mode 100644
index 0000000..a81e07e
--- /dev/null
+++ b/core/java/android/content/pm/SigningInfoException.java
@@ -0,0 +1,50 @@
+/*
+ * 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.content.pm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+/**
+ * Indicates an error when verifying the
+ * <a href="https://source.android.com/docs/security/features/apksigning">app signing</a>
+ * information.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class SigningInfoException extends Exception {
+    private final int mCode;
+
+    /** @hide */
+    public SigningInfoException(int code, @NonNull String message, @Nullable Throwable cause) {
+        super(message, cause);
+        mCode = code;
+    }
+
+    /**
+     * Returns a code representing the cause, in one of the installation parse return codes in
+     * {@link PackageManager}.
+     */
+    @FlaggedApi(Flags.FLAG_CLOUD_COMPILATION_PM)
+    public int getCode() {
+        return mCode;
+    }
+}
diff --git a/core/java/android/security/advancedprotection/OWNERS b/core/java/android/security/advancedprotection/OWNERS
index ddac8ed..bfb7e16 100644
--- a/core/java/android/security/advancedprotection/OWNERS
+++ b/core/java/android/security/advancedprotection/OWNERS
@@ -2,7 +2,6 @@
 
 achim@google.com
 azharaa@google.com
-cpinelli@google.com
 eranm@google.com
 hanikazmi@google.com
 haok@google.com
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index cab84bb..d1ef7bcf 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -201,6 +201,9 @@
      */
     public static final int DEBUG_ENABLE_PTRACE = 1 << 25;
 
+    /** Load 4KB ELF files on 16KB device using appcompat mode */
+    public static final int ENABLE_PAGE_SIZE_APP_COMPAT = 1 << 26;
+
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
     /** Default external storage should be mounted. */
diff --git a/core/java/com/android/internal/policy/IDeviceLockedStateListener.aidl b/core/java/com/android/internal/policy/IDeviceLockedStateListener.aidl
new file mode 100644
index 0000000..cc626f6
--- /dev/null
+++ b/core/java/com/android/internal/policy/IDeviceLockedStateListener.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy;
+
+oneway interface IDeviceLockedStateListener {
+    void onDeviceLockedStateChanged(boolean isDeviceLocked);
+}
\ No newline at end of file
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 284c299..aeaeeca 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -89,6 +89,7 @@
 
 #if defined(__BIONIC__)
 extern "C" void android_reset_stack_guards();
+extern "C" void android_set_16kb_appcompat_mode(bool enable_app_compat);
 #endif
 
 namespace {
@@ -350,6 +351,7 @@
     NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23,
     PROFILEABLE = 1 << 24,
     DEBUG_ENABLE_PTRACE = 1 << 25,
+    ENABLE_PAGE_SIZE_APP_COMPAT = 1 << 26,
 };
 
 enum UnsolicitedZygoteMessageTypes : uint32_t {
@@ -2117,6 +2119,12 @@
     SetCapabilities(permitted_capabilities, effective_capabilities, permitted_capabilities,
                     fail_fn);
 
+    if ((runtime_flags & RuntimeFlags::ENABLE_PAGE_SIZE_APP_COMPAT) != 0) {
+        android_set_16kb_appcompat_mode(true);
+        // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART
+        // runtime.
+        runtime_flags &= ~RuntimeFlags::ENABLE_PAGE_SIZE_APP_COMPAT;
+    }
     __android_log_close();
     AStatsSocket_close();
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 101ba11..d740655 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5428,7 +5428,8 @@
     <permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE"
         android:protectionLevel="signature" />
 
-    <!-- Allows an application to subscribe to keyguard locked (i.e., showing) state.
+    <!-- Allows an application to subscribe to device locked and keyguard locked (i.e., showing)
+        state.
          <p>Protection level: signature|role
          <p>Intended for use by ROLE_ASSISTANT and signature apps only.
     -->
diff --git a/core/tests/coretests/src/android/window/OWNERS b/core/tests/coretests/src/android/window/OWNERS
index 6c80cf9..b3fcea2 100644
--- a/core/tests/coretests/src/android/window/OWNERS
+++ b/core/tests/coretests/src/android/window/OWNERS
@@ -1,2 +1,3 @@
 include /services/core/java/com/android/server/wm/OWNERS
-charlesccchen@google.com
+
+# Bug component: 1519745 = per-file WindowContext*,WindowMetrics*,WindowProvider*,WindowTokenClient*
\ No newline at end of file
diff --git a/errorprone/Android.bp b/errorprone/Android.bp
index b559a15..1428b89 100644
--- a/errorprone/Android.bp
+++ b/errorprone/Android.bp
@@ -31,6 +31,14 @@
         "//external/auto:auto_service_annotations",
     ],
 
+    javacflags: [
+        // These exports are needed because this errorprone plugin access some private classes
+        // of the java compiler.
+        "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+        "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+        "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+    ],
+
     plugins: [
         "//external/auto:auto_service_plugin",
     ],
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index 4ab1fa1..6dfcf4ce 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -15,24 +15,23 @@
  */
 package android.platform.test.ravenwood;
 
-import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicMember;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
-import static org.junit.Assert.fail;
-
-import android.annotation.Nullable;
 import android.util.Log;
+import android.util.Pair;
 
-import com.android.ravenwood.common.RavenwoodRuntimeException;
+import com.android.ravenwood.RavenwoodRuntimeNative;
 
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 
-import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
 
 /**
- * Used to store various states associated with the current test runner that's inly needed
+ * Used to store various states associated with the current test runner that's only needed
  * in junit-impl.
  *
  * We don't want to put it in junit-src to avoid having to recompile all the downstream
@@ -42,6 +41,11 @@
  */
 public final class RavenwoodRunnerState {
     private static final String TAG = "RavenwoodRunnerState";
+    private static final String RAVENWOOD_RULE_ERROR =
+            "RavenwoodRule(s) are not executed in the correct order";
+
+    private static final List<Pair<RavenwoodRule, RavenwoodPropertyState>> sActiveProperties =
+            new ArrayList<>();
 
     private final RavenwoodAwareTestRunner mRunner;
 
@@ -52,30 +56,10 @@
         mRunner = runner;
     }
 
-    /**
-     * The RavenwoodConfig declared in the test class
-     */
-    private RavenwoodConfig mConfig;
-    /**
-     * The RavenwoodRule currently in effect, declared in the test class
-     */
-    private RavenwoodRule mRule;
-    private boolean mHasRavenwoodRule;
     private Description mMethodDescription;
 
     public void enterTestRunner() {
         Log.i(TAG, "enterTestRunner: " + mRunner);
-
-        mHasRavenwoodRule = hasRavenwoodRule(mRunner.mTestJavaClass);
-        mConfig = extractConfiguration(mRunner.mTestJavaClass);
-
-        if (mConfig != null) {
-            if (mHasRavenwoodRule) {
-                fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
-                        + " Suggest migrating to RavenwoodConfig.");
-            }
-        }
-
         RavenwoodRuntimeEnvironmentController.initForRunner();
     }
 
@@ -85,12 +69,8 @@
 
     public void exitTestClass() {
         Log.i(TAG, "exitTestClass: " + mRunner.mTestJavaClass.getName());
-        try {
-            RavenwoodRuntimeEnvironmentController.exitTestClass();
-        } finally {
-            mConfig = null;
-            mRule = null;
-        }
+        assertTrue(RAVENWOOD_RULE_ERROR, sActiveProperties.isEmpty());
+        RavenwoodRuntimeEnvironmentController.exitTestClass();
     }
 
     public void enterTestMethod(Description description) {
@@ -103,132 +83,68 @@
     }
 
     public void enterRavenwoodRule(RavenwoodRule rule) {
-        if (!mHasRavenwoodRule) {
-            fail("If you have a RavenwoodRule in your test, make sure the field type is"
-                    + " RavenwoodRule so Ravenwood can detect it.");
-        }
-        if (mRule != null) {
-            fail("Multiple nesting RavenwoodRule's are detected in the same class,"
-                    + " which is not supported.");
-        }
-        mRule = rule;
-        RavenwoodRuntimeEnvironmentController.setSystemProperties(rule.mSystemProperties);
+        pushTestProperties(rule);
     }
 
     public void exitRavenwoodRule(RavenwoodRule rule) {
-        if (mRule != rule) {
-            fail("RavenwoodRule did not take effect.");
-        }
-        mRule = null;
+        popTestProperties(rule);
     }
 
-    /**
-     * @return a configuration from a test class, if any.
-     */
-    @Nullable
-    private static RavenwoodConfig extractConfiguration(Class<?> testClass) {
-        var field = findConfigurationField(testClass);
-        if (field == null) {
-            return null;
+    static class RavenwoodPropertyState {
+
+        final List<Pair<String, String>> mBackup;
+        final Set<String> mKeyReadable;
+        final Set<String> mKeyWritable;
+
+        RavenwoodPropertyState(RavenwoodTestProperties props) {
+            mBackup = props.mValues.keySet().stream()
+                    .map(key -> Pair.create(key, RavenwoodRuntimeNative.getSystemProperty(key)))
+                    .toList();
+            mKeyReadable = Set.copyOf(props.mKeyReadable);
+            mKeyWritable = Set.copyOf(props.mKeyWritable);
         }
 
-        try {
-            return (RavenwoodConfig) field.get(null);
-        } catch (IllegalAccessException e) {
-            throw new RavenwoodRuntimeException("Failed to fetch from the configuration field", e);
+        boolean isKeyAccessible(String key, boolean write) {
+            return write ? mKeyWritable.contains(key) : mKeyReadable.contains(key);
         }
-    }
 
-    /**
-     * @return true if the current target class (or its super classes) has any @Rule / @ClassRule
-     * fields of type RavenwoodRule.
-     *
-     * Note, this check won't detect cases where a Rule is of type
-     * {@link TestRule} and still be a {@link RavenwoodRule}. But that'll be detected at runtime
-     * as a failure, in {@link #enterRavenwoodRule}.
-     */
-    private static boolean hasRavenwoodRule(Class<?> testClass) {
-        for (var field : testClass.getDeclaredFields()) {
-            if (!field.isAnnotationPresent(Rule.class)
-                    && !field.isAnnotationPresent(ClassRule.class)) {
-                continue;
-            }
-            if (field.getType().equals(RavenwoodRule.class)) {
-                return true;
-            }
-        }
-        // JUnit supports rules as methods, so we need to check them too.
-        for (var method : testClass.getDeclaredMethods()) {
-            if (!method.isAnnotationPresent(Rule.class)
-                    && !method.isAnnotationPresent(ClassRule.class)) {
-                continue;
-            }
-            if (method.getReturnType().equals(RavenwoodRule.class)) {
-                return true;
-            }
-        }
-        // Look into the super class.
-        if (!testClass.getSuperclass().equals(Object.class)) {
-            return hasRavenwoodRule(testClass.getSuperclass());
-        }
-        return false;
-    }
-
-    /**
-     * Find and return a field with @RavenwoodConfig.Config, which must be of type
-     * RavenwoodConfig.
-     */
-    @Nullable
-    private static Field findConfigurationField(Class<?> testClass) {
-        Field foundField = null;
-
-        for (var field : testClass.getDeclaredFields()) {
-            final var hasAnot = field.isAnnotationPresent(RavenwoodConfig.Config.class);
-            final var isType = field.getType().equals(RavenwoodConfig.class);
-
-            if (hasAnot) {
-                if (isType) {
-                    // Good, use this field.
-                    if (foundField != null) {
-                        fail(String.format(
-                                "Class %s has multiple fields with %s",
-                                testClass.getCanonicalName(),
-                                "@RavenwoodConfig.Config"));
-                    }
-                    // Make sure it's static public
-                    ensureIsPublicMember(field, true);
-
-                    foundField = field;
+        void restore() {
+            mBackup.forEach(pair -> {
+                if (pair.second == null) {
+                    RavenwoodRuntimeNative.removeSystemProperty(pair.first);
                 } else {
-                    fail(String.format(
-                            "Field %s.%s has %s but type is not %s",
-                            testClass.getCanonicalName(),
-                            field.getName(),
-                            "@RavenwoodConfig.Config",
-                            "RavenwoodConfig"));
-                    return null; // unreachable
+                    RavenwoodRuntimeNative.setSystemProperty(pair.first, pair.second);
                 }
-            } else {
-                if (isType) {
-                    fail(String.format(
-                            "Field %s.%s does not have %s but type is %s",
-                            testClass.getCanonicalName(),
-                            field.getName(),
-                            "@RavenwoodConfig.Config",
-                            "RavenwoodConfig"));
-                    return null; // unreachable
-                } else {
-                    // Unrelated field, ignore.
-                    continue;
-                }
-            }
+            });
         }
-        if (foundField != null) {
-            return foundField;
+    }
+
+    private static void pushTestProperties(RavenwoodRule rule) {
+        sActiveProperties.add(Pair.create(rule, new RavenwoodPropertyState(rule.mProperties)));
+        rule.mProperties.mValues.forEach(RavenwoodRuntimeNative::setSystemProperty);
+    }
+
+    private static void popTestProperties(RavenwoodRule rule) {
+        var pair = sActiveProperties.removeLast();
+        assertNotNull(RAVENWOOD_RULE_ERROR, pair);
+        assertEquals(RAVENWOOD_RULE_ERROR, rule, pair.first);
+        pair.second.restore();
+    }
+
+    @SuppressWarnings("unused")  // Called from native code (ravenwood_sysprop.cpp)
+    private static void checkSystemPropertyAccess(String key, boolean write) {
+        if (write && RavenwoodSystemProperties.sDefaultValues.containsKey(key)) {
+            // The default core values should never be modified
+            throw new IllegalArgumentException(
+                    "Setting core system property '" + key + "' is not allowed");
         }
-        if (!testClass.getSuperclass().equals(Object.class)) {
-            return findConfigurationField(testClass.getSuperclass());
+
+        final boolean result = RavenwoodSystemProperties.isKeyAccessible(key, write)
+                || sActiveProperties.stream().anyMatch(p -> p.second.isKeyAccessible(key, write));
+
+        if (!result) {
+            throw new IllegalArgumentException((write ? "Write" : "Read")
+                    + " access to system property '" + key + "' denied via RavenwoodRule");
         }
-        return null;
     }
 }
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index c2ed45d..e730a29 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -163,8 +163,6 @@
     @GuardedBy("sInitializationLock")
     private static Throwable sExceptionFromGlobalInit;
 
-    private static RavenwoodSystemProperties sProps;
-
     private static final int DEFAULT_TARGET_SDK_LEVEL = VERSION_CODES.CUR_DEVELOPMENT;
     private static final String DEFAULT_PACKAGE_NAME = "com.android.ravenwoodtests.defaultname";
 
@@ -234,7 +232,6 @@
 
         // Do the basic set up for the android sysprops.
         RavenwoodSystemProperties.initialize();
-        setSystemProperties(null);
 
         // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
         // before loadFrameworkNativeCode() (which uses $ANDROID_LOG_TAGS).
@@ -356,10 +353,13 @@
         // will call Mockito.framework().clearInlineMocks() after execution.
         sInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation());
 
+        // Reset some global state
         Process_ravenwood.reset();
         DeviceConfig_host.reset();
         Binder.restoreCallingIdentity(sCallingIdentity);
 
+        SystemProperties.clearChangeCallbacksForTest();
+
         if (ENABLE_TIMEOUT_STACKS) {
             sPendingTimeout = sTimeoutExecutor.schedule(
                     RavenwoodRuntimeEnvironmentController::dumpStacks,
@@ -484,19 +484,6 @@
         }
     }
 
-    /**
-     * Set the current configuration to the actual SystemProperties.
-     */
-    public static void setSystemProperties(@Nullable RavenwoodSystemProperties systemProperties) {
-        SystemProperties.clearChangeCallbacksForTest();
-        RavenwoodRuntimeNative.clearSystemProperties();
-        if (systemProperties == null) systemProperties = new RavenwoodSystemProperties();
-        sProps = new RavenwoodSystemProperties(systemProperties, true);
-        for (var entry : systemProperties.getValues().entrySet()) {
-            RavenwoodRuntimeNative.setSystemProperty(entry.getKey(), entry.getValue());
-        }
-    }
-
     private static final String MOCKITO_ERROR = "FATAL: Unsupported Mockito detected!"
             + " Your test or its dependencies use one of the \"mockito-target-*\""
             + " modules as static library, which is unusable on host side."
@@ -546,15 +533,6 @@
         return mock;
     }
 
-    @SuppressWarnings("unused")  // Called from native code (ravenwood_sysprop.cpp)
-    private static void checkSystemPropertyAccess(String key, boolean write) {
-        boolean result = write ? sProps.isKeyWritable(key) : sProps.isKeyReadable(key);
-        if (!result) {
-            throw new IllegalArgumentException((write ? "Write" : "Read")
-                    + " access to system property '" + key + "' denied via RavenwoodConfig");
-        }
-    }
-
     private static void dumpCommandLineArgs() {
         Log.i(TAG, "JVM arguments:");
 
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
similarity index 74%
rename from ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
rename to ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index 9bd376a..c545baa 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.platform.test.ravenwood;
 
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
@@ -21,26 +20,30 @@
 
 import android.util.Log;
 
+import com.android.ravenwood.RavenwoodRuntimeNative;
+
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
+/**
+ * A class to manage the core default system properties of the Ravenwood environment.
+ */
 public class RavenwoodSystemProperties {
     private static final String TAG = "RavenwoodSystemProperties";
 
-    /** We pull in propeties from this file. */
+    /** We pull in properties from this file. */
     private static final String RAVENWOOD_BUILD_PROP = "ravenwood-data/ravenwood-build.prop";
 
     /** This is the actual build.prop we use to build the device (contents depends on lunch). */
     private static final String DEVICE_BUILD_PROP = "ravenwood-data/build.prop";
 
     /** The default values. */
-    private static final Map<String, String> sDefaultValues = new HashMap<>();
+    static final Map<String, String> sDefaultValues = new HashMap<>();
 
     private static final String[] PARTITIONS = {
             "bootimage",
@@ -91,7 +94,7 @@
                 var deviceValue = deviceProps.get(deviceKey);
                 if (deviceValue == null) {
                     throw new RuntimeException("Failed to initialize system properties. Key '"
-                             + deviceKey + "' doesn't exist in the device side build.prop");
+                            + deviceKey + "' doesn't exist in the device side build.prop");
                 }
                 value = deviceValue;
             } else {
@@ -115,6 +118,7 @@
                 }
             }
         }
+
         if (RAVENWOOD_VERBOSE_LOGGING) {
             // Dump all properties for local debugging.
             Log.v(TAG, "All system properties:");
@@ -122,35 +126,12 @@
                 Log.v(TAG, "" + key + "=" + sDefaultValues.get(key));
             }
         }
+
+        // Actually set the system properties
+        sDefaultValues.forEach(RavenwoodRuntimeNative::setSystemProperty);
     }
 
-    private volatile boolean mIsImmutable;
-
-    private final Map<String, String> mValues = new HashMap<>();
-
-    /** Set of additional keys that should be considered readable */
-    private final Set<String> mKeyReadable = new HashSet<>();
-
-    /** Set of additional keys that should be considered writable */
-    private final Set<String> mKeyWritable = new HashSet<>();
-
-    public RavenwoodSystemProperties() {
-        mValues.putAll(sDefaultValues);
-    }
-
-    /** Copy constructor */
-    public RavenwoodSystemProperties(RavenwoodSystemProperties source, boolean immutable) {
-        mKeyReadable.addAll(source.mKeyReadable);
-        mKeyWritable.addAll(source.mKeyWritable);
-        mValues.putAll(source.mValues);
-        mIsImmutable = immutable;
-    }
-
-    public Map<String, String> getValues() {
-        return new HashMap<>(mValues);
-    }
-
-    public boolean isKeyReadable(String key) {
+    private static boolean isKeyReadable(String key) {
         final String root = getKeyRoot(key);
 
         if (root.startsWith("debug.")) return true;
@@ -183,10 +164,10 @@
                 return true;
         }
 
-        return mKeyReadable.contains(key);
+        return false;
     }
 
-    public boolean isKeyWritable(String key) {
+    private static boolean isKeyWritable(String key) {
         final String root = getKeyRoot(key);
 
         if (root.startsWith("debug.")) return true;
@@ -194,42 +175,11 @@
         // For PropertyInvalidatedCache
         if (root.startsWith("cache_key.")) return true;
 
-        return mKeyWritable.contains(key);
+        return false;
     }
 
-    private void ensureNotImmutable() {
-        if (mIsImmutable) {
-            throw new RuntimeException("Unable to update immutable instance");
-        }
-    }
-
-    public void setValue(String key, Object value) {
-        ensureNotImmutable();
-
-        final String valueString = (value == null) ? null : String.valueOf(value);
-        if ((valueString == null) || valueString.isEmpty()) {
-            mValues.remove(key);
-        } else {
-            mValues.put(key, valueString);
-        }
-    }
-
-    public void setAccessNone(String key) {
-        ensureNotImmutable();
-        mKeyReadable.remove(key);
-        mKeyWritable.remove(key);
-    }
-
-    public void setAccessReadOnly(String key) {
-        ensureNotImmutable();
-        mKeyReadable.add(key);
-        mKeyWritable.remove(key);
-    }
-
-    public void setAccessReadWrite(String key) {
-        ensureNotImmutable();
-        mKeyReadable.add(key);
-        mKeyWritable.add(key);
+    static boolean isKeyAccessible(String key, boolean write) {
+        return write ? isKeyWritable(key) : isKeyReadable(key);
     }
 
     /**
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 6262ad1..e49d3d9 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -92,7 +92,7 @@
         }
     }
 
-    final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+    final RavenwoodTestProperties mProperties = new RavenwoodTestProperties();
 
     public static class Builder {
 
@@ -144,8 +144,8 @@
          * Has no effect on non-Ravenwood environments.
          */
         public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) {
-            mRule.mSystemProperties.setValue(key, value);
-            mRule.mSystemProperties.setAccessReadOnly(key);
+            mRule.mProperties.setValue(key, value);
+            mRule.mProperties.setAccessReadOnly(key);
             return this;
         }
 
@@ -160,8 +160,8 @@
          * Has no effect on non-Ravenwood environments.
          */
         public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) {
-            mRule.mSystemProperties.setValue(key, value);
-            mRule.mSystemProperties.setAccessReadWrite(key);
+            mRule.mProperties.setValue(key, value);
+            mRule.mProperties.setAccessReadWrite(key);
             return this;
         }
 
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodTestProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodTestProperties.java
new file mode 100644
index 0000000..66a26b5
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodTestProperties.java
@@ -0,0 +1,59 @@
+/*
+ * 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.platform.test.ravenwood;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class to store system properties defined by tests.
+ */
+public class RavenwoodTestProperties {
+    final Map<String, String> mValues = new HashMap<>();
+
+    /** Set of additional keys that should be considered readable */
+    final Set<String> mKeyReadable = new HashSet<>();
+
+    /** Set of additional keys that should be considered writable */
+    final Set<String> mKeyWritable = new HashSet<>();
+
+    public void setValue(String key, Object value) {
+        final String valueString = (value == null) ? null : String.valueOf(value);
+        if ((valueString == null) || valueString.isEmpty()) {
+            mValues.remove(key);
+        } else {
+            mValues.put(key, valueString);
+        }
+    }
+
+    public void setAccessNone(String key) {
+        mKeyReadable.remove(key);
+        mKeyWritable.remove(key);
+    }
+
+    public void setAccessReadOnly(String key) {
+        mKeyReadable.add(key);
+        mKeyWritable.remove(key);
+    }
+
+    public void setAccessReadWrite(String key) {
+        mKeyReadable.add(key);
+        mKeyWritable.add(key);
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index 7b940b4..9a78989 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -56,7 +56,11 @@
 
     public static native boolean setSystemProperty(String key, String value);
 
-    public static native void clearSystemProperties();
+    public static native boolean removeSystemProperty(String key);
+
+    public static void clearSystemProperties() {
+        removeSystemProperty(null);
+    }
 
     public static native int gettid();
 
diff --git a/ravenwood/runtime-jni/jni_helper.h b/ravenwood/runtime-jni/jni_helper.h
index 561fb3b..25d7519 100644
--- a/ravenwood/runtime-jni/jni_helper.h
+++ b/ravenwood/runtime-jni/jni_helper.h
@@ -26,6 +26,7 @@
 constexpr const char* kCommonUtils = "com/android/ravenwood/common/RavenwoodCommonUtils";
 constexpr const char* kRuntimeEnvController =
         "android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController";
+constexpr const char* kRunnerState = "android/platform/test/ravenwood/RavenwoodRunnerState";
 constexpr const char* kRuntimeNative = "com/android/ravenwood/RavenwoodRuntimeNative";
 
 // We have to explicitly decode the string to real UTF-8, because when using GetStringUTFChars
diff --git a/ravenwood/runtime-jni/ravenwood_sysprop.cpp b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
index aafc426..a78aa8d 100644
--- a/ravenwood/runtime-jni/ravenwood_sysprop.cpp
+++ b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
@@ -117,7 +117,7 @@
 // ---- JNI ----
 
 static JavaVM* gVM = nullptr;
-static jclass gEnvController = nullptr;
+static jclass gRunnerState = nullptr;
 static jmethodID gCheckSystemPropertyAccess;
 
 static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
@@ -128,11 +128,11 @@
 
 // Call back into Java code to check property access
 static void check_system_property_access(const char* key, bool write) {
-    if (gVM != nullptr && gEnvController != nullptr) {
+    if (gVM != nullptr && gRunnerState != nullptr) {
         JNIEnv* env;
         if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) {
             ALOGI("%s access to system property '%s'", write ? "Write" : "Read", key);
-            env->CallStaticVoidMethod(gEnvController, gCheckSystemPropertyAccess,
+            env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess,
                                       env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE);
             return;
         }
@@ -155,16 +155,29 @@
     return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE;
 }
 
-static void clearSystemProperties(JNIEnv*, jclass) {
+static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
     std::lock_guard lock(g_properties_lock);
-    g_properties.clear();
+
+    if (javaKey == nullptr) {
+        g_properties.clear();
+        return JNI_TRUE;
+    } else {
+        ScopedUtfChars key(env, javaKey);
+        auto it = g_properties.find(key);
+        if (it != g_properties.end()) {
+            g_properties.erase(it);
+            return JNI_TRUE;
+        } else {
+            return JNI_FALSE;
+        }
+    }
 }
 
 static const JNINativeMethod sMethods[] = {
         {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary},
         {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty},
         {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty},
-        {"clearSystemProperties", "()V", (void*)clearSystemProperties},
+        {"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty},
 };
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
@@ -174,9 +187,9 @@
     gVM = vm;
 
     // Fetch several references for future use
-    gEnvController = FindGlobalClassOrDie(env, kRuntimeEnvController);
+    gRunnerState = FindGlobalClassOrDie(env, kRunnerState);
     gCheckSystemPropertyAccess =
-            GetStaticMethodIDOrDie(env, gEnvController, "checkSystemPropertyAccess",
+            GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess",
                                    "(Ljava/lang/String;Z)V");
 
     // Expose raw property methods as JNI methods
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
deleted file mode 100644
index c25d2b4..0000000
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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 com.android.ravenwoodtest.bivalenttest;
-
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-/**
- * Make sure having multiple RavenwoodRule's is detected.
- * (But only when running on ravenwod. Otherwise it'll be ignored.)
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodMultipleRuleTest {
-
-    @Rule(order = Integer.MIN_VALUE)
-    public final ExpectedException mExpectedException = ExpectedException.none();
-
-    @Rule
-    public final RavenwoodRule mRavenwood1 = new RavenwoodRule();
-
-    @Rule
-    public final RavenwoodRule mRavenwood2 = new RavenwoodRule();
-
-    public RavenwoodMultipleRuleTest() {
-        // We can't call it within the test method because the exception happens before
-        // calling the method, so set it up here.
-        if (RavenwoodConfig.isOnRavenwood()) {
-            mExpectedException.expectMessage("Multiple nesting RavenwoodRule");
-        }
-    }
-
-    @Test
-    public void testMultipleRulesNotAllowed() {
-        Assume.assumeTrue(RavenwoodConfig.isOnRavenwood());
-    }
-}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java
new file mode 100644
index 0000000..f9e73db
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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 com.android.ravenwoodtest.runnercallbacktests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemProperties;
+import android.platform.test.annotations.NoRavenizer;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for RavenwoodRule.
+ */
+@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
+public class RavenwoodRuleValidationTest extends RavenwoodRunnerTestBase {
+
+    public static class RuleInBaseClass {
+        static String PROPERTY_KEY = "debug.ravenwood.prop.in.base";
+        static String PROPERTY_VAL = "ravenwood";
+        @Rule
+        public final RavenwoodRule mRavenwood1 = new RavenwoodRule.Builder()
+                .setSystemPropertyImmutable(PROPERTY_KEY, PROPERTY_VAL).build();
+    }
+
+    /**
+     * Make sure that RavenwoodRule in a base class takes effect.
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest
+    testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest)
+    testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest
+    testSuiteFinished: classes
+    testRunFinished: 1,0,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class RuleInBaseClassSuccessTest extends RuleInBaseClass {
+
+        @Test
+        public void testRuleInBaseClass() {
+            assertThat(SystemProperties.get(PROPERTY_KEY)).isEqualTo(PROPERTY_VAL);
+        }
+    }
+
+    /**
+     * Same as {@link RuleInBaseClass}, but the type of the rule field is not {@link RavenwoodRule}.
+     */
+    public abstract static class RuleWithDifferentTypeInBaseClass {
+        static String PROPERTY_KEY = "debug.ravenwood.prop.in.base.different.type";
+        static String PROPERTY_VAL = "ravenwood";
+        @Rule
+        public final TestRule mRavenwood1 = new RavenwoodRule.Builder()
+                .setSystemPropertyImmutable(PROPERTY_KEY, PROPERTY_VAL).build();
+    }
+
+    /**
+     * Make sure that RavenwoodRule in a base class takes effect, even if the field type is not
+     */
+    @RunWith(AndroidJUnit4.class)
+    // CHECKSTYLE:OFF
+    @Expected("""
+    testRunStarted: classes
+    testSuiteStarted: classes
+    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
+    testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+    testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
+    testSuiteFinished: classes
+    testRunFinished: 1,0,0,0
+    """)
+    // CHECKSTYLE:ON
+    public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
+
+        @Test
+        public void testRuleInBaseClass() {
+            assertThat(SystemProperties.get(PROPERTY_KEY)).isEqualTo(PROPERTY_VAL);
+        }
+    }
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
deleted file mode 100644
index f94b98b..0000000
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * 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 com.android.ravenwoodtest.runnercallbacktests;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.NoRavenizer;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-
-
-/**
- * Test for @Config field extraction and validation.
- *
- * TODO(b/377765941) Most of the tests here will be obsolete and deleted with b/377765941, but
- * some of the tests may need to be re-implemented one way or another. (e.g. the package name
- * test.) Until that happens, we'll keep all tests here but add an {@code @Ignore} instead.
- */
-@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
-public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase {
-    public abstract static class ConfigInBaseClass {
-        static String PACKAGE_NAME = "com.ConfigInBaseClass";
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
-                .setPackageName(PACKAGE_NAME).build();
-    }
-
-    /**
-     * Make sure a config in the base class is detected.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest
-    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest)
-    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest
-    testSuiteFinished: classes
-    testRunFinished: 1,0,0,0
-    """)
-    // CHECKSTYLE:ON
-    @Ignore // Package name is no longer set via config.
-    public static class ConfigInBaseClassTest extends ConfigInBaseClass {
-        @Test
-        public void test() {
-            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
-                    .isEqualTo(PACKAGE_NAME);
-        }
-    }
-
-    /**
-     * Make sure a config in the base class is detected.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest
-    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest)
-    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest
-    testSuiteFinished: classes
-    testRunFinished: 1,0,0,0
-    """)
-    // CHECKSTYLE:ON
-    @Ignore // Package name is no longer set via config.
-    public static class ConfigOverridingTest extends ConfigInBaseClass {
-        static String PACKAGE_NAME_OVERRIDE = "com.ConfigOverridingTest";
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
-                .setPackageName(PACKAGE_NAME_OVERRIDE).build();
-
-        @Test
-        public void test() {
-            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
-                    .isEqualTo(PACKAGE_NAME_OVERRIDE);
-        }
-    }
-
-    /**
-     * Test to make sure that if a test has a config error, the failure would be reported from
-     * each test method.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
-    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest.sConfig expected to be public static
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class ErrorMustBeReportedFromEachTest {
-        @RavenwoodConfig.Config
-        private static RavenwoodConfig sConfig = // Invalid because it's private.
-                new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void testMethod1() {
-        }
-
-        @Test
-        public void testMethod2() {
-        }
-
-        @Test
-        public void testMethod3() {
-        }
-    }
-
-    /**
-     * Invalid because there are two @Config's.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
-    testFailure: Class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.DuplicateConfigTest has multiple fields with @RavenwoodConfig.Config
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class DuplicateConfigTest {
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig1 =
-                new RavenwoodConfig.Builder().build();
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig2 =
-                new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-    }
-
-    /**
-     * @Config's must be static.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
-    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest.sConfig expected to be public static
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class NonStaticConfigTest {
-
-        @RavenwoodConfig.Config
-        public RavenwoodConfig sConfig =
-                new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-    }
-
-    /**
-     * @Config's must be public.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
-    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest.sConfig expected to be public static
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class NonPublicConfigTest {
-
-        @RavenwoodConfig.Config
-        RavenwoodConfig sConfig =
-                new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-    }
-
-    /**
-     * @Config's must be of type RavenwoodConfig.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
-    testFailure: Field com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.WrongTypeConfigTest.sConfig has @RavenwoodConfig.Config but type is not RavenwoodConfig
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class WrongTypeConfigTest {
-
-        @RavenwoodConfig.Config
-        public static Object sConfig =
-                new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-
-    }
-
-    /**
-     * @Rule must be of type RavenwoodRule.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest
-    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest)
-    testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
-    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class WrongTypeRuleTest {
-
-        @Rule
-        public TestRule mRule = new RavenwoodRule.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-
-    }
-
-    /**
-     * Config can't be used with a (instance) Rule.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
-    testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class WithInstanceRuleTest {
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig =
-                new RavenwoodConfig.Builder().build();
-
-        @Rule
-        public RavenwoodRule mRule = new RavenwoodRule.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-    }
-
-    /**
-     * Config can't be used with a (static) Rule.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
-    testFailure: Failed to instantiate class androidx.test.ext.junit.runners.AndroidJUnit4
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class WithStaticRuleTest {
-
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig =
-                new RavenwoodConfig.Builder().build();
-
-        @Rule
-        public static RavenwoodRule sRule = new RavenwoodRule.Builder().build();
-
-        @Test
-        public void testConfig() {
-        }
-    }
-
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest
-    testStarted: testMultipleRulesNotAllowed(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest)
-    testFailure: Multiple nesting RavenwoodRule's are detected in the same class, which is not supported.
-    testFinished: testMultipleRulesNotAllowed(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class DuplicateRulesTest {
-
-        @Rule
-        public final RavenwoodRule mRavenwood1 = new RavenwoodRule();
-
-        @Rule
-        public final RavenwoodRule mRavenwood2 = new RavenwoodRule();
-
-        @Test
-        public void testMultipleRulesNotAllowed() {
-        }
-    }
-
-    public static class RuleInBaseClass {
-        static String PACKAGE_NAME = "com.RuleInBaseClass";
-        @Rule
-        public final RavenwoodRule mRavenwood1 = new RavenwoodRule.Builder()
-                .setPackageName(PACKAGE_NAME).build();
-    }
-
-    /**
-     * Make sure that RavenwoodRule in a base class takes effect.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest
-    testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest)
-    testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest
-    testSuiteFinished: classes
-    testRunFinished: 1,0,0,0
-    """)
-    // CHECKSTYLE:ON
-    @Ignore // Package name is no longer set via config.
-    public static class RuleInBaseClassSuccessTest extends RuleInBaseClass {
-
-        @Test
-        public void testRuleInBaseClass() {
-            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
-                    .isEqualTo(PACKAGE_NAME);
-        }
-    }
-
-    /**
-     * Make sure that having a config and a rule in a base class should fail.
-     * RavenwoodRule.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
-    testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
-    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class ConfigWithRuleInBaseClassTest extends RuleInBaseClass {
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void test() {
-        }
-    }
-
-    /**
-     * Same as {@link RuleInBaseClass}, but the type of the rule field is not {@link RavenwoodRule}.
-     */
-    public abstract static class RuleWithDifferentTypeInBaseClass {
-        static String PACKAGE_NAME = "com.RuleWithDifferentTypeInBaseClass";
-        @Rule
-        public final TestRule mRavenwood1 = new RavenwoodRule.Builder()
-                .setPackageName(PACKAGE_NAME).build();
-    }
-
-    /**
-     * Make sure that RavenwoodRule in a base class takes effect, even if the field type is not
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
-    testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
-    testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
-    testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    @Ignore // Package name is no longer set via config.
-    public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
-
-        @Test
-        public void testRuleInBaseClass() {
-            assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
-                    .isEqualTo(PACKAGE_NAME);
-        }
-    }
-
-    /**
-     * Make sure that having a config and a rule in a base class should fail, even if the field type is not
-     * RavenwoodRule.
-     */
-    @RunWith(AndroidJUnit4.class)
-    // CHECKSTYLE:OFF
-    @Expected("""
-    testRunStarted: classes
-    testSuiteStarted: classes
-    testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
-    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
-    testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
-    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
-    testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
-    testSuiteFinished: classes
-    testRunFinished: 1,1,0,0
-    """)
-    // CHECKSTYLE:ON
-    public static class ConfigWithRuleWithDifferentTypeInBaseClassTest extends RuleWithDifferentTypeInBaseClass {
-        @RavenwoodConfig.Config
-        public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder().build();
-
-        @Test
-        public void test() {
-        }
-    }
-}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
index 8e04b69..271c27f 100644
--- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
@@ -15,7 +15,6 @@
  */
 package com.android.ravenwoodtest.runtimetest;
 
-import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
 import static android.os.Process.FIRST_APPLICATION_UID;
 
 import static org.junit.Assert.assertEquals;
@@ -23,7 +22,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Process;
-import android.platform.test.ravenwood.RavenwoodConfig;
 import android.system.Os;
 
 import com.android.ravenwood.RavenwoodRuntimeState;
@@ -34,13 +32,6 @@
 
 public class IdentityTest {
 
-    @RavenwoodConfig.Config
-    public static final RavenwoodConfig sConfig =
-            new RavenwoodConfig.Builder()
-                    .setTargetSdkLevel(UPSIDE_DOWN_CAKE)
-                    .setProcessApp()
-                    .build();
-
     @Test
     public void testUid() {
         assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid);
@@ -60,7 +51,7 @@
     @Test
     public void testTargetSdkLevel() {
         assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT);
-        assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel);
-        assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion());
+        assertEquals(RavenwoodRuntimeState.sTargetSdkLevel,
+                VMRuntime.getRuntime().getTargetSdkVersion());
     }
 }
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/SystemPropertyTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/SystemPropertyTest.java
new file mode 100644
index 0000000..70bf204
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/SystemPropertyTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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 com.android.ravenwoodtest.runtimetest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemProperties;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+
+public class SystemPropertyTest {
+
+    private static final String PROP_KEY_1 = "debug.ravenwood.prop1";
+    private static final String PROP_VAL_1 = "ravenwood.1";
+    private static final String PROP_KEY_2 = "debug.ravenwood.prop2";
+    private static final String PROP_VAL_2 = "ravenwood.2";
+    private static final String PROP_KEY_3 = "debug.ravenwood.prop3";
+    private static final String PROP_VAL_3 = "ravenwood.3";
+    private static final String PROP_VAL_4 = "ravenwood.4";
+
+    @ClassRule(order = 0)
+    public static TestRule mCheckClassRule = (base, description) -> new Statement() {
+        @Override
+        public void evaluate() throws Throwable {
+            assertTrue(SystemProperties.get(PROP_KEY_1).isEmpty());
+            assertTrue(SystemProperties.get(PROP_KEY_3).isEmpty());
+            try {
+                base.evaluate();
+            } finally {
+                assertTrue(SystemProperties.get(PROP_KEY_1).isEmpty());
+                assertTrue(SystemProperties.get(PROP_KEY_3).isEmpty());
+            }
+        }
+    };
+
+    @ClassRule(order = 1)
+    public static RavenwoodRule mClassRule = new RavenwoodRule.Builder()
+            .setSystemPropertyImmutable(PROP_KEY_1, PROP_VAL_1)
+            .setSystemPropertyImmutable(PROP_KEY_3, PROP_VAL_4)
+            .build();
+
+    @Rule(order = 0)
+    public TestRule mCheckRule = (base, description) -> new Statement() {
+        @Override
+        public void evaluate() throws Throwable {
+            assertTrue(SystemProperties.get(PROP_KEY_2).isEmpty());
+            assertEquals(SystemProperties.get(PROP_KEY_3), PROP_VAL_4);
+            try {
+                base.evaluate();
+            } finally {
+                assertTrue(SystemProperties.get(PROP_KEY_2).isEmpty());
+                assertEquals(SystemProperties.get(PROP_KEY_3), PROP_VAL_4);
+            }
+        }
+    };
+
+    @Rule(order = 1)
+    public RavenwoodRule mRule = new RavenwoodRule.Builder()
+            .setSystemPropertyImmutable(PROP_KEY_2, PROP_VAL_2)
+            .setSystemPropertyImmutable(PROP_KEY_3, PROP_VAL_3)
+            .build();
+
+    @Test
+    public void testRavenwoodRuleSetProperty() {
+        assertEquals(SystemProperties.get(PROP_KEY_1), PROP_VAL_1);
+        assertEquals(SystemProperties.get(PROP_KEY_2), PROP_VAL_2);
+        assertEquals(SystemProperties.get(PROP_KEY_3), PROP_VAL_3);
+    }
+}
diff --git a/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index eeb7110..99981f4 100644
--- a/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
+++ b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -24,8 +24,6 @@
 import android.content.Context;
 import android.hardware.SerialManager;
 import android.hardware.SerialManagerInternal;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodConfig.Config;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -42,12 +40,6 @@
 public class RavenwoodServicesTest {
     private static final String TEST_VIRTUAL_PORT = "virtual:example";
 
-    @Config
-    public static final RavenwoodConfig sRavenwood = new RavenwoodConfig.Builder()
-            .setProcessSystem()
-            .setServicesRequired(SerialManager.class)
-            .build();
-
     private Context mContext;
 
     @Before
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 953aae9..585870bc 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -21,6 +21,7 @@
 import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -60,6 +61,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -83,6 +85,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.infra.AndroidFuture;
+import com.android.internal.policy.IDeviceLockedStateListener;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
@@ -103,6 +106,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.IntStream;
 
 /**
  * Manages trust agents and trust listeners.
@@ -250,6 +254,10 @@
             new SparseArray<>();
     private final SparseArray<TrustableTimeoutAlarmListener>
             mIdleTrustableTimeoutAlarmListenerForUser = new SparseArray<>();
+
+    private final RemoteCallbackList<IDeviceLockedStateListener>
+            mDeviceLockedStateListeners = new RemoteCallbackList<>();
+
     private AlarmManager mAlarmManager;
     private final Object mAlarmLock = new Object();
 
@@ -1085,6 +1093,7 @@
         if (changed) {
             notifyTrustAgentsOfDeviceLockState(userId, locked);
             notifyKeystoreOfDeviceLockState(userId, locked);
+            notifyDeviceLockedListenersForUser(userId, locked);
             // Also update the user's profiles who have unified challenge, since they
             // share the same unlocked state (see {@link #isDeviceLocked(int)})
             for (int profileHandle : mUserManager.getEnabledProfileIds(userId)) {
@@ -1892,6 +1901,26 @@
             return mIsInSignificantPlace;
         }
 
+        @EnforcePermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+        @Override
+        public void registerDeviceLockedStateListener(IDeviceLockedStateListener listener,
+                int deviceId) {
+            super.registerDeviceLockedStateListener_enforcePermission();
+            if (deviceId != Context.DEVICE_ID_DEFAULT) {
+                // Virtual devices are considered insecure.
+                return;
+            }
+            mDeviceLockedStateListeners.register(listener,
+                    Integer.valueOf(UserHandle.getUserId(Binder.getCallingUid())));
+        }
+
+        @EnforcePermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+        @Override
+        public void unregisterDeviceLockedStateListener(IDeviceLockedStateListener listener) {
+            super.unregisterDeviceLockedStateListener_enforcePermission();
+            mDeviceLockedStateListeners.unregister(listener);
+        }
+
         private void enforceReportPermission() {
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events");
@@ -2013,6 +2042,7 @@
                     }
 
                     notifyKeystoreOfDeviceLockState(userId, locked);
+                    notifyDeviceLockedListenersForUser(userId, locked);
 
                     if (locked) {
                         try {
@@ -2479,4 +2509,26 @@
             updateTrust(mUserId, 0 /* flags */);
         }
     }
+
+    private void notifyDeviceLockedListenersForUser(int userId, boolean locked) {
+        synchronized (mDeviceLockedStateListeners) {
+            int numListeners = mDeviceLockedStateListeners.beginBroadcast();
+            try {
+                IntStream.range(0, numListeners).forEach(i -> {
+                    try {
+                        Integer uid = (Integer) mDeviceLockedStateListeners.getBroadcastCookie(i);
+                        if (userId == uid.intValue()) {
+                            mDeviceLockedStateListeners.getBroadcastItem(i)
+                                    .onDeviceLockedStateChanged(locked);
+                        }
+                    } catch (RemoteException re) {
+                        Log.i(TAG, "Service died", re);
+                    }
+                });
+
+            } finally {
+                mDeviceLockedStateListeners.finishBroadcast();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 63cd59e..dcad8b0 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -19,6 +19,7 @@
 wilsonshih@google.com
 jiamingliu@google.com
 pdwilliams@google.com
+charlesccchen@google.com
 
 # Files related to background activity launches
 per-file Background*Start* = set noparent