Merge "Use getParameter return value for the preset length" into main
diff --git a/ACTIVITY_SECURITY_OWNERS b/ACTIVITY_SECURITY_OWNERS
new file mode 100644
index 0000000..c39842e
--- /dev/null
+++ b/ACTIVITY_SECURITY_OWNERS
@@ -0,0 +1,2 @@
+haok@google.com
+wnan@google.com
\ No newline at end of file
diff --git a/INTENT_OWNERS b/INTENT_OWNERS
index 58b5f2a..c828215 100644
--- a/INTENT_OWNERS
+++ b/INTENT_OWNERS
@@ -1,3 +1,4 @@
 include /PACKAGE_MANAGER_OWNERS
 include /services/core/java/com/android/server/wm/OWNERS
 include /services/core/java/com/android/server/am/OWNERS
+include /ACTIVITY_SECURITY_OWNERS
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
index 3577fcd..f20b170 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
@@ -194,7 +194,7 @@
     /**
      * Simple benchmark for the amount of time to send a given number of messages
      */
-    // @Test Temporarily disabled
+    @Test
     @Parameters(method = "getParams")
     public void time(Config config) throws Exception {
         reset();
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
index ac57100..af3c405 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
@@ -198,7 +198,7 @@
         executor.awaitTermination(5, TimeUnit.SECONDS);
     }
 
-    // @Test Temporarily disabled
+    @Test
     @Parameters(method = "getParams")
     public void throughput(Config config) throws Exception {
         setup(config);
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index 33f6899..ecb9a73 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -444,8 +444,13 @@
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-            switch (intent.getAction()) {
+            switch (action) {
                 case Intent.ACTION_USER_REMOVED:
                     if (userId > 0) {
                         mHandler.doUserRemoved(userId);
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index a37779e..3e650da 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -666,7 +666,12 @@
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
+            final String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+
+            switch (action) {
                 case ConnectivityManager.CONNECTIVITY_ACTION: {
                     updateConnectivityState(intent);
                 } break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index e3ac780..7a21697 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -84,9 +84,13 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+
             final String pkgName = getPackageName(intent);
             final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-            final String action = intent.getAction();
             if (pkgUid == -1) {
                 Slog.e(TAG, "Didn't get package UID in intent (" + action + ")");
                 return;
diff --git a/core/api/current.txt b/core/api/current.txt
index 2c4c146..6b342a5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16232,6 +16232,7 @@
     field public static final int UNKNOWN = 0; // 0x0
     field public static final int Y8 = 538982489; // 0x20203859
     field public static final int YCBCR_P010 = 54; // 0x36
+    field @FlaggedApi("android.media.codec.p210_format_support") public static final int YCBCR_P210 = 60; // 0x3c
     field public static final int YUV_420_888 = 35; // 0x23
     field public static final int YUV_422_888 = 39; // 0x27
     field public static final int YUV_444_888 = 40; // 0x28
@@ -18580,6 +18581,7 @@
     field public static final long USAGE_VIDEO_ENCODE = 65536L; // 0x10000L
     field public static final int YCBCR_420_888 = 35; // 0x23
     field public static final int YCBCR_P010 = 54; // 0x36
+    field @FlaggedApi("android.media.codec.p210_format_support") public static final int YCBCR_P210 = 60; // 0x3c
   }
 
   @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public final class OverlayProperties implements android.os.Parcelable {
@@ -22871,6 +22873,7 @@
     field public static final int COLOR_FormatYUV444Flexible = 2135181448; // 0x7f444888
     field @Deprecated public static final int COLOR_FormatYUV444Interleaved = 29; // 0x1d
     field public static final int COLOR_FormatYUVP010 = 54; // 0x36
+    field @FlaggedApi("android.media.codec.p210_format_support") public static final int COLOR_FormatYUVP210 = 60; // 0x3c
     field @Deprecated public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 2141391872; // 0x7fa30c00
     field @Deprecated public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100
     field public static final String FEATURE_AdaptivePlayback = "adaptive-playback";
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
old mode 100644
new mode 100755
index 69c3bd3..0264f73
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -61,6 +61,8 @@
     oneway void shutdown();
     void executeShellCommandWithStderr(String command, in ParcelFileDescriptor sink,
                 in ParcelFileDescriptor source, in ParcelFileDescriptor stderrSink);
+    void executeShellCommandArrayWithStderr(in String[] command, in ParcelFileDescriptor sink,
+                in ParcelFileDescriptor source, in ParcelFileDescriptor stderrSink);
     List<String> getAdoptedShellPermissions();
     void addOverridePermissionState(int uid, String permission, int result);
     void removeOverridePermissionState(int uid, String permission);
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 27f9f54..cb31bc7 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -250,6 +250,7 @@
  * @hide
  */
 @TestApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PropertyInvalidatedCache<Query, Result> {
     /**
      * This is a configuration class that customizes a cache instance.
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
old mode 100644
new mode 100755
index 3c4bd9e..72db465
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -553,7 +553,12 @@
         } catch (IOException exc) {
             throw new RuntimeException("Error running shell command '" + command + "'", exc);
         }
+        handleExecuteShellCommandProcess(process, sink, source, stderrSink);
+    }
 
+    private void handleExecuteShellCommandProcess(final java.lang.Process process,
+            final ParcelFileDescriptor sink, final ParcelFileDescriptor source,
+            final ParcelFileDescriptor stderrSink) {
         // Read from process and write to pipe
         final Thread readFromProcess;
         if (sink != null) {
@@ -616,6 +621,26 @@
     }
 
     @Override
+    public void executeShellCommandArrayWithStderr(final String[] command,
+            final ParcelFileDescriptor sink, final ParcelFileDescriptor source,
+            final ParcelFileDescriptor stderrSink) throws RemoteException {
+        synchronized (mLock) {
+            throwIfCalledByNotTrustedUidLocked();
+            throwIfShutdownLocked();
+            throwIfNotConnectedLocked();
+        }
+        final java.lang.Process process;
+
+        try {
+            process = Runtime.getRuntime().exec(command);
+        } catch (IOException exc) {
+            throw new RuntimeException(
+                    "Error running shell command '" + String.join(" ", command) + "'", exc);
+        }
+        handleExecuteShellCommandProcess(process, sink, source, stderrSink);
+    }
+
+    @Override
     public void shutdown() {
         synchronized (mLock) {
             if (isConnectedLocked()) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index bca30b4..8de86d5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -146,6 +146,7 @@
      * This exception is thrown when a given package, application, or component
      * name cannot be found.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static class NameNotFoundException extends AndroidException {
         public NameNotFoundException() {
         }
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index 8ea450c..41585b3 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -53,7 +53,7 @@
 
         return new BulkCursorProxy(obj);
     }
-    
+
     @Override
     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
             throws RemoteException {
@@ -79,7 +79,7 @@
                     reply.writeNoException();
                     return true;
                 }
-                
+
                 case CLOSE_TRANSACTION: {
                     data.enforceInterface(IBulkCursor.descriptor);
                     close();
@@ -212,15 +212,22 @@
         Parcel reply = Parcel.obtain();
         try {
             data.writeInterfaceToken(IBulkCursor.descriptor);
-
-            mRemote.transact(CLOSE_TRANSACTION, data, reply, 0);
-            DatabaseUtils.readExceptionFromParcel(reply);
+            // If close() is being called from the finalizer thread, do not wait for a reply from
+            // the remote side.
+            final boolean fromFinalizer =
+                    android.database.sqlite.Flags.onewayFinalizerClose()
+                    && "FinalizerDaemon".equals(Thread.currentThread().getName());
+            mRemote.transact(CLOSE_TRANSACTION, data, reply,
+                    fromFinalizer ? IBinder.FLAG_ONEWAY: 0);
+            if (!fromFinalizer) {
+                DatabaseUtils.readExceptionFromParcel(reply);
+            }
         } finally {
             data.recycle();
             reply.recycle();
         }
     }
-    
+
     public int requery(IContentObserver observer) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -282,4 +289,3 @@
         }
     }
 }
-
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 285f984..c597895 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -2,6 +2,13 @@
 container: "system"
 
 flag {
+     name: "oneway_finalizer_close"
+     namespace: "system_performance"
+     description: "Make BuildCursorNative.close oneway if in the the finalizer"
+     bug: "368221351"
+}
+
+flag {
      name: "sqlite_apis_35"
      is_exported: true
      namespace: "system_performance"
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index ce0f9f59..0e73978 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -66,6 +66,7 @@
             DS_FP32UI8,
             S_UI8,
             YCBCR_P010,
+            YCBCR_P210,
             R_8,
             R_16,
             RG_1616,
@@ -111,6 +112,16 @@
      * little-endian value, with the lower 6 bits set to zero.
      */
     public static final int YCBCR_P010    = 0x36;
+    /**
+     * <p>Android YUV P210 format.</p>
+     *
+     * P210 is a 4:2:2 YCbCr semiplanar format comprised of a WxH Y plane
+     * followed by a WxH CbCr plane. Each sample is represented by a 16-bit
+     * little-endian value, with the lower 6 bits set to zero.
+     */
+    @FlaggedApi(android.media.codec.Flags.FLAG_P210_FORMAT_SUPPORT)
+    public static final int YCBCR_P210    = 0x3c;
+
     /** Format: 8 bits red */
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
     public static final int R_8           = 0x38;
diff --git a/core/java/android/net/vcn/OWNERS b/core/java/android/net/vcn/OWNERS
index 2441e77..937699a 100644
--- a/core/java/android/net/vcn/OWNERS
+++ b/core/java/android/net/vcn/OWNERS
@@ -1,7 +1,6 @@
 set noparent
 
-benedictwong@google.com
-ckesting@google.com
 evitayan@google.com
-junyin@google.com
 nharold@google.com
+benedictwong@google.com #{LAST_RESORT_SUGGESTION}
+yangji@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 5b30624..1adefe5 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -10,8 +10,26 @@
 }
 
 flag {
+     name: "mainline_vcn_module_api"
+     namespace: "vcn"
+     description: "Expose APIs from VCN for mainline migration"
+     is_exported: true
+     bug: "376339506"
+}
+
+flag {
     name: "safe_mode_timeout_config"
     namespace: "vcn"
     description: "Feature flag for adjustable safe mode timeout"
     bug: "317406085"
+}
+
+flag {
+    name: "fix_config_garbage_collection"
+    namespace: "vcn"
+    description: "Handle race condition in subscription change"
+    bug: "370862489"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index bf44d65..d7a308d 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -242,6 +242,7 @@
  */
 @TestApi
 @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, Result> {
     /**
      * {@inheritDoc}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2acda8a..e402ddf 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -249,6 +249,10 @@
         return isExperimentEnabled("profilesystemserver");
     }
 
+    private static boolean shouldProfileBootClasspath() {
+        return isExperimentEnabled("profilebootclasspath");
+    }
+
     /**
      * Performs Zygote process initialization. Loads and initializes commonly used classes.
      *
@@ -352,7 +356,7 @@
             // If we are profiling the boot image, reset the Jit counters after preloading the
             // classes. We want to preload for performance, and we can use method counters to
             // infer what clases are used after calling resetJitCounters, for profile purposes.
-            if (isExperimentEnabled("profilebootclasspath")) {
+            if (shouldProfileBootClasspath()) {
                 Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ResetJitCounters");
                 VMRuntime.resetJitCounters();
                 Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
@@ -460,12 +464,28 @@
                             ? String.join(":", systemServerClasspath, standaloneSystemServerJars)
                             : systemServerClasspath;
                     prepareSystemServerProfile(systemServerPaths);
+                    try {
+                        SystemProperties.set("debug.tracing.profile_system_server", "1");
+                    } catch (RuntimeException e) {
+                        Slog.e(TAG, "Failed to set debug.tracing.profile_system_server", e);
+                    }
                 } catch (Exception e) {
                     Log.wtf(TAG, "Failed to set up system server profile", e);
                 }
             }
         }
 
+        // Zygote can't set system properties due to permission denied. We need to be in System
+        // Server to set system properties, so we do it here instead of the more natural place in
+        // preloadClasses.
+        if (shouldProfileBootClasspath()) {
+            try {
+                SystemProperties.set("debug.tracing.profile_boot_classpath", "1");
+            } catch (RuntimeException e) {
+                Slog.e(TAG, "Failed to set debug.tracing.profile_boot_classpath", e);
+            }
+        }
+
         if (parsedArgs.mInvokeWith != null) {
             String[] args = parsedArgs.mRemainingArgs;
             // If we have a non-null system server class path, we'll have to duplicate the
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 89fdeeb..933781c 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -14,17 +14,19 @@
  * limitations under the License.
  */
 
+#include <core_jni_helpers.h>
+#include <cutils/trace.h>
 #include <fcntl.h>
+#include <minikin/Hyphenator.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <tracing_perfetto.h>
+#include <unicode/uloc.h>
 #include <unistd.h>
 
 #include <algorithm>
 
-#include <core_jni_helpers.h>
-#include <minikin/Hyphenator.h>
-
 namespace android {
 
 static std::string buildFileName(const std::string& locale) {
@@ -79,6 +81,23 @@
     minikin::addHyphenatorAlias(from, to);
 }
 
+/*
+ * Cache the subtag key map by calling uloc_forLanguageTag with a subtag.
+ * minikin calls uloc_forLanguageTag with an Unicode extension specifying
+ * the line breaking strictness. Parsing the extension requires loading the key map
+ * from keyTypeData.res in the ICU.
+ * "lb" is the key commonly used by minikin. "ca" is a common legacy key mapping to
+ * the "calendar" key. It ensures that the key map is loaded and cached in icu4c.
+ * "en-Latn-US" is a common locale used in the Android system regardless what default locale
+ * is selected in the Settings app.
+ */
+inline static void cacheUnicodeExtensionSubtagsKeyMap() {
+    UErrorCode status = U_ZERO_ERROR;
+    char localeID[ULOC_FULLNAME_CAPACITY] = {};
+    uloc_forLanguageTag("en-Latn-US-u-lb-loose-ca-gregory", localeID, ULOC_FULLNAME_CAPACITY,
+                        nullptr, &status);
+}
+
 static void init() {
     // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but that
     // appears too small.
@@ -190,6 +209,10 @@
     addHyphenatorAlias("und-Orya", "or");  // Oriya
     addHyphenatorAlias("und-Taml", "ta");  // Tamil
     addHyphenatorAlias("und-Telu", "te");  // Telugu
+
+    tracing_perfetto::traceBegin(ATRACE_TAG_VIEW, "CacheUnicodeExtensionSubtagsKeyMap");
+    cacheUnicodeExtensionSubtagsKeyMap();
+    tracing_perfetto::traceEnd(ATRACE_TAG_VIEW); // CacheUnicodeExtensionSubtagsKeyMap
 }
 
 static const JNINativeMethod gMethods[] = {
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 7af3073..b1c48ab 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -256,6 +256,7 @@
         "src/android/content/ContextTest.java",
         "src/android/content/pm/PackageManagerTest.java",
         "src/android/content/pm/UserInfoTest.java",
+        "src/android/app/PropertyInvalidatedCacheTests.java",
         "src/android/database/CursorWindowTest.java",
         "src/android/os/**/*.java",
         "src/android/telephony/PinResultTest.java",
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index cd6abdd..95a4fe4 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -39,11 +39,7 @@
  *  atest FrameworksCoreTests:PropertyInvalidatedCacheTests
  */
 @SmallTest
-@IgnoreUnderRavenwood(blockedBy = PropertyInvalidatedCache.class)
 public class PropertyInvalidatedCacheTests {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
     // Configuration for creating caches
     private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST;
     private static final String API = "testApi";
diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
index b03fd64..5edf0ca 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -37,10 +37,7 @@
  *  atest FrameworksCoreTests:IpcDataCacheTest
  */
 @SmallTest
-@IgnoreUnderRavenwood(blockedBy = IpcDataCache.class)
 public class IpcDataCacheTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     // Configuration for creating caches
     private static final String MODULE = IpcDataCache.MODULE_TEST;
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index cb3b64c..93d94c9 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 
 import java.lang.annotation.Retention;
@@ -41,6 +42,7 @@
              Y8,
              Y16,
              YCBCR_P010,
+             YCBCR_P210,
              NV16,
              NV21,
              YUY2,
@@ -206,6 +208,26 @@
     public static final int YCBCR_P010 = 0x36;
 
     /**
+     * <p>Android YUV P210 format.</p>
+     *
+     * P210 is a 4:2:2 YCbCr semiplanar format comprised of a WxH Y plane
+     * followed by a WxH CbCr plane. Each sample is represented by a 16-bit
+     * little-endian value, with the lower 6 bits set to zero.
+     *
+     * <p>For example, the {@link android.media.Image} object can provide data
+     * in this format from a {@link android.hardware.camera2.CameraDevice}
+     * through a {@link android.media.ImageReader} object if this format is
+     * supported by {@link android.hardware.camera2.CameraDevice}.</p>
+     *
+     * @see android.media.Image
+     * @see android.media.ImageReader
+     * @see android.hardware.camera2.CameraDevice
+     *
+     */
+    @FlaggedApi(android.media.codec.Flags.FLAG_P210_FORMAT_SUPPORT)
+    public static final int YCBCR_P210 = 0x3c;
+
+    /**
      * YCbCr format, used for video.
      *
      * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
@@ -849,6 +871,8 @@
                 return 16;
             case YCBCR_P010:
                 return 24;
+            case YCBCR_P210:
+                return 32;
             case RAW_DEPTH10:
             case RAW10:
                 return 10;
@@ -899,7 +923,9 @@
             case JPEG_R:
                 return true;
         }
-
+        if (android.media.codec.Flags.p210FormatSupport() && format == YCBCR_P210) {
+            return true;
+        }
         return false;
     }
 }
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 7f936f2..344d19a 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -23,9 +23,6 @@
 import android.annotation.SystemApi;
 import android.os.Process;
 import android.security.keymaster.KeymasterDefs;
-
-import libcore.util.EmptyArray;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.spec.AlgorithmParameterSpec;
@@ -33,6 +30,7 @@
 import java.security.spec.MGF1ParameterSpec;
 import java.util.Collection;
 import java.util.Locale;
+import libcore.util.EmptyArray;
 
 /**
  * Properties of <a href="{@docRoot}training/articles/keystore.html">Android Keystore</a> keys.
@@ -116,7 +114,7 @@
     public static final int PURPOSE_AGREE_KEY = 1 << 6;
 
     /**
-     * Purpose of key: Signing attestaions. This purpose is incompatible with all others, meaning
+     * Purpose of key: Signing attestations. This purpose is incompatible with all others, meaning
      * that when generating a key with PURPOSE_ATTEST_KEY, no other purposes may be specified. In
      * addition, PURPOSE_ATTEST_KEY may not be specified for imported keys.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 4988a94..851472f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -25,6 +25,7 @@
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
 import android.window.IBackAnimationRunner;
 import android.window.IOnBackInvokedCallback;
 
@@ -32,6 +33,8 @@
 import com.android.internal.jank.Cuj.CujType;
 import com.android.wm.shell.common.InteractionJankMonitorUtils;
 
+import java.lang.ref.WeakReference;
+
 /**
  * Used to register the animation callback and runner, it will trigger result if gesture was finish
  * before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
@@ -80,22 +83,49 @@
         return mCallback;
     }
 
+    private Runnable mFinishedCallback;
+    private RemoteAnimationTarget[] mApps;
+    private IRemoteAnimationFinishedCallback mRemoteCallback;
+
+    private static class RemoteAnimationFinishedStub extends IRemoteAnimationFinishedCallback.Stub {
+        //the binder callback should not hold strong reference to it to avoid memory leak.
+        private WeakReference<BackAnimationRunner> mRunnerRef;
+
+        private RemoteAnimationFinishedStub(BackAnimationRunner runner) {
+            mRunnerRef = new WeakReference<>(runner);
+        }
+
+        @Override
+        public void onAnimationFinished() {
+            BackAnimationRunner runner = mRunnerRef.get();
+            if (runner == null) {
+                return;
+            }
+            if (runner.shouldMonitorCUJ(runner.mApps)) {
+                InteractionJankMonitorUtils.endTracing(runner.mCujType);
+            }
+
+            runner.mFinishedCallback.run();
+            for (int i = runner.mApps.length - 1; i >= 0; --i) {
+                 SurfaceControl sc = runner.mApps[i].leash;
+                 if (sc != null && sc.isValid()) {
+                     sc.release();
+                 }
+            }
+            runner.mApps = null;
+            runner.mFinishedCallback = null;
+        }
+    }
+
     /**
      * Called from {@link IBackAnimationRunner}, it will deliver these
      * {@link RemoteAnimationTarget}s to the corresponding runner.
      */
     void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
             RemoteAnimationTarget[] nonApps, Runnable finishedCallback) {
-        final IRemoteAnimationFinishedCallback callback =
-                new IRemoteAnimationFinishedCallback.Stub() {
-                    @Override
-                    public void onAnimationFinished() {
-                        if (shouldMonitorCUJ(apps)) {
-                            InteractionJankMonitorUtils.endTracing(mCujType);
-                        }
-                        finishedCallback.run();
-                    }
-                };
+        mFinishedCallback = finishedCallback;
+        mApps = apps;
+        if (mRemoteCallback == null) mRemoteCallback = new RemoteAnimationFinishedStub(this);
         mWaitingAnimation = false;
         if (shouldMonitorCUJ(apps)) {
             InteractionJankMonitorUtils.beginTracing(
@@ -103,7 +133,7 @@
         }
         try {
             getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
-                    nonApps, callback);
+                    nonApps, mRemoteCallback);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed call onAnimationStart", e);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS
new file mode 100644
index 0000000..752d2fd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS
@@ -0,0 +1,2 @@
+# WM Shell sub-module dagger owners
+jorgegil@google.com
\ No newline at end of file
diff --git a/libs/hwui/platform/host/android/api-level.h b/libs/hwui/platform/host/android/api-level.h
deleted file mode 120000
index 4fb4784..0000000
--- a/libs/hwui/platform/host/android/api-level.h
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../../bionic/libc/include/android/api-level.h
\ No newline at end of file
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 8ff4305..3a19f46 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -462,6 +462,33 @@
         @SuppressLint("AllUpper")
         public static final int COLOR_FormatYUVP010                 = 54;
 
+        /**
+         * P210 is 10-bit-per component 4:2:2 YCbCr semiplanar format.
+         * <p>
+         * This format uses 32 allocated bits per pixel with 20 bits of
+         * data per pixel. Chroma planes are subsampled by 2 both
+         * horizontally. Each chroma and luma component
+         * has 16 allocated bits in little-endian configuration with 10
+         * MSB of actual data.
+         *
+         * <pre>
+         *            byte                   byte
+         *  <--------- i --------> | <------ i + 1 ------>
+         * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+         * |     UNUSED      |      Y/Cb/Cr                |
+         * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+         *  0               5 6   7 0                    7
+         * bit
+         * </pre>
+         *
+         * Use this format with {@link Image}. This format corresponds
+         * to {@link android.graphics.ImageFormat#YCBCR_P210}.
+         * <p>
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(android.media.codec.Flags.FLAG_P210_FORMAT_SUPPORT)
+        public static final int COLOR_FormatYUVP210                 = 60;
+
         /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
         public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 0x7f000100;
         // COLOR_FormatSurface indicates that the data will be a GraphicBuffer metadata reference.
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index 5e55f64..678150b 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -584,45 +584,108 @@
      * The following table summarizes codec support for containers across android releases:
      *
      * <table>
-     *  <thead>
-     *   <tr>
-     *    <th rowspan=2>OS Version(s)</th>
-     *    <td colspan=3>Codec support</th>
-     *   </tr><tr>
-     *    <th>{@linkplain OutputFormat#MUXER_OUTPUT_MPEG_4 MP4}</th>
-     *    <th>{@linkplain OutputFormat#MUXER_OUTPUT_WEBM WEBM}</th>
-     *   </tr>
-     *  </thead>
-     *  <tbody>
-     *   <tr>
-     *    <td>{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}</td>
-     *    <td rowspan=6>{@link MediaFormat#MIMETYPE_AUDIO_AAC AAC},<br>
-     *        {@link MediaFormat#MIMETYPE_AUDIO_AMR_NB NB-AMR},<br>
-     *        {@link MediaFormat#MIMETYPE_AUDIO_AMR_WB WB-AMR},<br>
-     *        {@link MediaFormat#MIMETYPE_VIDEO_H263 H.263},<br>
-     *        {@link MediaFormat#MIMETYPE_VIDEO_MPEG4 MPEG-4},<br>
-     *        {@link MediaFormat#MIMETYPE_VIDEO_AVC AVC} (H.264)</td>
-     *    <td rowspan=3>Not supported</td>
-     *   </tr><tr>
-     *    <td>{@link android.os.Build.VERSION_CODES#KITKAT}</td>
-     *   </tr><tr>
-     *    <td>{@link android.os.Build.VERSION_CODES#KITKAT_WATCH}</td>
-     *   </tr><tr>
-     *    <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td>
-     *    <td rowspan=3>{@link MediaFormat#MIMETYPE_AUDIO_VORBIS Vorbis},<br>
-     *        {@link MediaFormat#MIMETYPE_VIDEO_VP8 VP8}</td>
-     *   </tr><tr>
-     *    <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td>
-     *   </tr><tr>
-     *    <td>{@link android.os.Build.VERSION_CODES#M}</td>
-     *   </tr><tr>
-     *    <td>{@link android.os.Build.VERSION_CODES#N}</td>
-     *    <td>as above, plus<br>
-     *        {@link MediaFormat#MIMETYPE_VIDEO_HEVC HEVC} (H.265)</td>
-     *    <td>as above, plus<br>
-     *        {@link MediaFormat#MIMETYPE_VIDEO_VP9 VP9}</td>
-     *   </tr>
-     *  </tbody>
+     *   <thead>
+     *     <tr>
+     *       <th>Codec</th>
+     *       <th>{@linkplain OutputFormat#MUXER_OUTPUT_MPEG_4 MP4}</th>
+     *       <th>{@linkplain OutputFormat#MUXER_OUTPUT_WEBM WEBM}</th>
+     *       <th>{@linkplain OutputFormat#MUXER_OUTPUT_OGG OGG}</th>
+     *       <th>Supported From SDK version</th>
+     *     </tr>
+     *   </thead>
+     *   <tbody>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_AUDIO_AAC AAC}</td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td></td>
+     *       <td>17</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_AUDIO_AMR_NB NB-AMR}</td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td></td>
+     *       <td>17</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_AUDIO_AMR_WB WB-AMR}</td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td></td>
+     *       <td>17</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_VIDEO_H263 H.263}</td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td></td>
+     *       <td>17</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_VIDEO_MPEG4 MPEG-4}</td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td></td>
+     *       <td>17</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_VIDEO_AVC AVC} (H.264)</td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td></td>
+     *       <td>17</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_AUDIO_VORBIS Vorbis}</td>
+     *       <td></td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td>21</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_VIDEO_VP8 VP8}</td>
+     *       <td></td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td>21</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_VIDEO_VP9 VP9}</td>
+     *       <td></td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td>24</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_VIDEO_HEVC HEVC} (H.265)</td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td></td>
+     *       <td>24</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_AUDIO_OPUS OPUS}</td>
+     *       <td></td>
+     *       <td>✓</td>
+     *       <td>✓</td>
+     *       <td>26</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_VIDEO_AV1 AV1}</td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td></td>
+     *       <td>31</td>
+     *     </tr>
+     *     <tr>
+     *       <td>{@link MediaFormat#MIMETYPE_VIDEO_DOLBY_VISION Dolby Vision}</td>
+     *       <td>✓</td>
+     *       <td></td>
+     *       <td></td>
+     *       <td>32</td>
+     *     </tr>
+     *   </tbody>
      * </table>
      *
      * @param format The media format for the track.  This must not be an empty
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 371e3d2..b0c280b 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -935,6 +935,11 @@
         return static_cast<jint>(PublicFormat::PRIVATE);
     } else {
         BufferItem* buffer = Image_getBufferItem(env, thiz);
+        if (buffer == nullptr) {
+            jniThrowException(env, "java/lang/IllegalStateException",
+                    "Image is not initialized");
+            return -1;
+        }
         int readerHalFormat = mapPublicFormatToHalFormat(static_cast<PublicFormat>(readerFormat));
         int32_t fmt = applyFormatOverrides(
                 buffer->mGraphicBuffer->getPixelFormat(), readerHalFormat);
@@ -953,6 +958,11 @@
 
 static jobject Image_getHardwareBuffer(JNIEnv* env, jobject thiz) {
     BufferItem* buffer = Image_getBufferItem(env, thiz);
+    if (buffer == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                "Image is not initialized");
+        return NULL;
+    }
     AHardwareBuffer* b = AHardwareBuffer_from_GraphicBuffer(buffer->mGraphicBuffer.get());
     // don't user the public AHardwareBuffer_toHardwareBuffer() because this would force us
     // to link against libandroid.so
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
index 121f549..3696000 100644
--- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
@@ -41,6 +41,7 @@
             "/system_ext/etc/NOTICE.xml.gz",
             "/vendor_dlkm/etc/NOTICE.xml.gz",
             "/odm_dlkm/etc/NOTICE.xml.gz",
+            "/system_dlkm/etc/NOTICE.xml.gz",
     };
     static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
 
diff --git a/packages/SystemUI/animation/lib/OWNERS b/packages/SystemUI/animation/lib/OWNERS
new file mode 100644
index 0000000..7569419
--- /dev/null
+++ b/packages/SystemUI/animation/lib/OWNERS
@@ -0,0 +1,3 @@
+#inherits OWNERS from SystemUI in addition to WEAR framework owners below
+file:platform/frameworks/base:/WEAR_OWNERS
+
diff --git a/packages/Vcn/TEST_MAPPING b/packages/Vcn/TEST_MAPPING
new file mode 100644
index 0000000..bde88fe
--- /dev/null
+++ b/packages/Vcn/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "postsubmit": [
+    {
+      "name": "FrameworksVcnTests"
+    },
+    {
+      "name": "CtsVcnTestCases"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 9a5e623b..65ea9fe 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -170,7 +170,7 @@
         "hoststubgen-helper-runtime.ravenwood",
         "mockito-ravenwood-prebuilt",
     ],
-    visibility: ["//frameworks/base"],
+    visibility: [":__subpackages__"],
     jarjar_rules: ":ravenwood-services-jarjar-rules",
 }
 
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 72f62c5..a1243e3 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -5,8 +5,8 @@
     { "name": "hoststubgen-test-tiny-test" },
     { "name": "hoststubgen-invoke-test" },
     { "name": "RavenwoodMockitoTest_device" },
-    // TODO(b/371215487): Re-enable when the test is fixed.
-    // { "name": "RavenwoodBivalentTest_device" },
+    { "name": "RavenwoodBivalentTest_device" },
+    { "name": "RavenwoodBivalentTest_device_ravenizer" },
 
     { "name": "RavenwoodBivalentInstTest_nonself_inst" },
     { "name": "RavenwoodBivalentInstTest_self_inst_device" },
@@ -35,18 +35,18 @@
     },
     {
       "name": "CarLibHostUnitTest",
-      "host": true,
-      "keywords": ["automotive_code_coverage"]
+      "keywords": ["automotive_code_coverage"],
+      "host": true
     },
     {
       "name": "CarServiceHostUnitTest",
-      "host": true,
-      "keywords": ["automotive_code_coverage"]
+      "keywords": ["automotive_code_coverage"],
+      "host": true
     },
     {
       "name": "CarSystemUIRavenTests",
-      "host": true,
-      "keywords": ["automotive_code_coverage"]
+      "keywords": ["automotive_code_coverage"],
+      "host": true
     },
     {
       "name": "CtsAccountManagerTestCasesRavenwood",
@@ -65,6 +65,10 @@
       "host": true
     },
     {
+      "name": "CtsDeviceConfigTestCasesRavenwood",
+      "host": true
+    },
+    {
       "name": "CtsGraphicsTestCasesRavenwood",
       "host": true
     },
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
new file mode 100644
index 0000000..66a6890
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -0,0 +1,404 @@
+/*
+ * 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 static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
+import android.platform.test.annotations.internal.InnerRunner;
+import android.platform.test.ravenwood.RavenwoodTestStats.Result;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Suite;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.util.function.BiConsumer;
+
+/**
+ * A test runner used for Ravenwood.
+ *
+ * It will delegate to another runner specified with {@link InnerRunner}
+ * (default = {@link BlockJUnit4ClassRunner}) with the following features.
+ * - Add a called before the inner runner gets a chance to run. This can be used to initialize
+ *   stuff used by the inner runner.
+ * - Add hook points with help from the four test rules such as {@link #sImplicitClassOuterRule},
+ *   which are also injected by the ravenizer tool.
+ *
+ * We use this runner to:
+ * - Initialize the Ravenwood environment.
+ * - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}.
+ */
+public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase {
+    public static final String TAG = "Ravenwood";
+
+    /** Scope of a hook. */
+    public enum Scope {
+        Class,
+        Instance,
+    }
+
+    /** Order of a hook. */
+    public enum Order {
+        Outer,
+        Inner,
+    }
+
+    private record HookRule(Scope scope, Order order) implements TestRule {
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return getCurrentRunner().wrapWithHooks(base, description, scope, order);
+        }
+    }
+
+    // The following four rule instances will be injected to tests by the Ravenizer tool.
+    public static final TestRule sImplicitClassOuterRule = new HookRule(Scope.Class, Order.Outer);
+    public static final TestRule sImplicitClassInnerRule = new HookRule(Scope.Class, Order.Inner);
+    public static final TestRule sImplicitInstOuterRule = new HookRule(Scope.Instance, Order.Outer);
+    public static final TestRule sImplicitInstInnerRule = new HookRule(Scope.Instance, Order.Inner);
+
+    /** Keeps track of the runner on the current thread. */
+    private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>();
+
+    private static RavenwoodAwareTestRunner getCurrentRunner() {
+        var runner = sCurrentRunner.get();
+        if (runner == null) {
+            throw new RuntimeException("Current test runner not set!");
+        }
+        return runner;
+    }
+
+    final Class<?> mTestJavaClass;
+    private final Runner mRealRunner;
+    private TestClass mTestClass = null;
+
+    /**
+     * Stores internal states / methods associated with this runner that's only needed in
+     * junit-impl.
+     */
+    final RavenwoodRunnerState mState = new RavenwoodRunnerState(this);
+
+    /**
+     * Constructor.
+     */
+    public RavenwoodAwareTestRunner(Class<?> testClass) {
+        RavenwoodRuntimeEnvironmentController.globalInitOnce();
+        mTestJavaClass = testClass;
+
+        /*
+         * If the class has @DisabledOnRavenwood, then we'll delegate to
+         * ClassSkippingTestRunner, which simply skips it.
+         *
+         * We need to do it before instantiating TestClass for b/367694651.
+         */
+        if (!RavenwoodEnablementChecker.shouldRunClassOnRavenwood(testClass, true)) {
+            mRealRunner = new ClassSkippingTestRunner(testClass);
+            return;
+        }
+
+        mTestClass = new TestClass(testClass);
+
+        Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
+
+        // This is needed to make AndroidJUnit4ClassRunner happy.
+        InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
+
+        // Hook point to allow more customization.
+        runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
+
+        mRealRunner = instantiateRealRunner(mTestClass);
+
+        mState.enterTestRunner();
+    }
+
+    @Override
+    Runner getRealRunner() {
+        return mRealRunner;
+    }
+
+    private void runAnnotatedMethodsOnRavenwood(Class<? extends Annotation> annotationClass,
+            Object instance) {
+        Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
+
+        for (var method : mTestClass.getAnnotatedMethods(annotationClass)) {
+            ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null);
+
+            var methodDesc = method.getDeclaringClass().getName() + "."
+                    + method.getMethod().toString();
+            try {
+                method.getMethod().invoke(instance);
+            } catch (IllegalAccessException | InvocationTargetException e) {
+                throw logAndFail("Caught exception while running method " + methodDesc, e);
+            }
+        }
+    }
+
+    @Override
+    public void run(RunNotifier realNotifier) {
+        final var notifier = new RavenwoodRunNotifier(realNotifier);
+        final var description = getDescription();
+
+        if (mRealRunner instanceof ClassSkippingTestRunner) {
+            mRealRunner.run(notifier);
+            Log.i(TAG, "onClassSkipped: description=" + description);
+            RavenwoodTestStats.getInstance().onClassSkipped(description);
+            return;
+        }
+
+        Log.v(TAG, "Starting " + mTestJavaClass.getCanonicalName());
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            dumpDescription(description);
+        }
+
+        // TODO(b/365976974): handle nested classes better
+        final boolean skipRunnerHook =
+                mRealRunnerTakesRunnerBuilder && mRealRunner instanceof Suite;
+
+        sCurrentRunner.set(this);
+        try {
+            if (!skipRunnerHook) {
+                try {
+                    mState.enterTestClass();
+                } catch (Throwable th) {
+                    notifier.reportBeforeTestFailure(description, th);
+                    return;
+                }
+            }
+
+            // Delegate to the inner runner.
+            mRealRunner.run(notifier);
+        } finally {
+            sCurrentRunner.remove();
+
+            if (!skipRunnerHook) {
+                try {
+                    RavenwoodTestStats.getInstance().onClassFinished(description);
+                    mState.exitTestClass();
+                } catch (Throwable th) {
+                    notifier.reportAfterTestFailure(th);
+                }
+            }
+        }
+    }
+
+    private Statement wrapWithHooks(Statement base, Description description, Scope scope,
+            Order order) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                runWithHooks(description, scope, order, base);
+            }
+        };
+    }
+
+    private void runWithHooks(Description description, Scope scope, Order order, Statement s)
+            throws Throwable {
+        assumeTrue(onBefore(description, scope, order));
+        try {
+            s.evaluate();
+            onAfter(description, scope, order, null);
+        } catch (Throwable t) {
+            if (onAfter(description, scope, order, t)) {
+                throw t;
+            }
+        }
+    }
+
+    /**
+     * A runner that simply skips a class. It still has to support {@link Filterable}
+     * because otherwise the result still says "SKIPPED" even when it's not included in the
+     * filter.
+     */
+    private static class ClassSkippingTestRunner extends Runner implements Filterable {
+        private final Description mDescription;
+        private boolean mFilteredOut;
+
+        ClassSkippingTestRunner(Class<?> testClass) {
+            mDescription = Description.createTestDescription(testClass, testClass.getSimpleName());
+            mFilteredOut = false;
+        }
+
+        @Override
+        public Description getDescription() {
+            return mDescription;
+        }
+
+        @Override
+        public void run(RunNotifier notifier) {
+            if (mFilteredOut) {
+                return;
+            }
+            notifier.fireTestSuiteStarted(mDescription);
+            notifier.fireTestIgnored(mDescription);
+            notifier.fireTestSuiteFinished(mDescription);
+        }
+
+        @Override
+        public void filter(Filter filter) throws NoTestsRemainException {
+            if (filter.shouldRun(mDescription)) {
+                mFilteredOut = false;
+            } else {
+                throw new NoTestsRemainException();
+            }
+        }
+    }
+
+    /**
+     * Called before a test / class.
+     *
+     * Return false if it should be skipped.
+     */
+    private boolean onBefore(Description description, Scope scope, Order order) {
+        Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order);
+
+        if (scope == Scope.Instance && order == Order.Outer) {
+            // Start of a test method.
+            mState.enterTestMethod(description);
+        }
+
+        final var classDescription = getDescription();
+
+        // Class-level annotations are checked by the runner already, so we only check
+        // method-level annotations here.
+        if (scope == Scope.Instance && order == Order.Outer) {
+            if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(description, true)) {
+                RavenwoodTestStats.getInstance().onTestFinished(
+                        classDescription, description, Result.Skipped);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Called after a test / class.
+     *
+     * Return false if the exception should be ignored.
+     */
+    private boolean onAfter(Description description, Scope scope, Order order, Throwable th) {
+        Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
+
+        final var classDescription = getDescription();
+
+        if (scope == Scope.Instance && order == Order.Outer) {
+            // End of a test method.
+            mState.exitTestMethod();
+
+            final Result result;
+            if (th == null) {
+                result = Result.Passed;
+            } else if (th instanceof AssumptionViolatedException) {
+                result = Result.Skipped;
+            } else {
+                result = Result.Failed;
+            }
+
+            RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
+        }
+
+        // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
+        if (RavenwoodRule.private$ravenwood().isRunningDisabledTests()
+                && scope == Scope.Instance && order == Order.Outer) {
+
+            boolean isTestEnabled = RavenwoodEnablementChecker.shouldEnableOnRavenwood(
+                    description, false);
+            if (th == null) {
+                // Test passed. Is the test method supposed to be enabled?
+                if (isTestEnabled) {
+                    // Enabled and didn't throw, okay.
+                    return true;
+                } else {
+                    // Disabled and didn't throw. We should report it.
+                    fail("Test wasn't included under Ravenwood, but it actually "
+                            + "passed under Ravenwood; consider updating annotations");
+                    return true; // unreachable.
+                }
+            } else {
+                // Test failed.
+                if (isTestEnabled) {
+                    // Enabled but failed. We should throw the exception.
+                    return true;
+                } else {
+                    // Disabled and failed. Expected. Don't throw.
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Called by RavenwoodRule.
+     */
+    static void onRavenwoodRuleEnter(Description description, RavenwoodRule rule) {
+        Log.v(TAG, "onRavenwoodRuleEnter: description=" + description);
+        getCurrentRunner().mState.enterRavenwoodRule(rule);
+    }
+
+    /**
+     * Called by RavenwoodRule.
+     */
+    static void onRavenwoodRuleExit(Description description, RavenwoodRule rule) {
+        Log.v(TAG, "onRavenwoodRuleExit: description=" + description);
+        getCurrentRunner().mState.exitRavenwoodRule(rule);
+    }
+
+    private void dumpDescription(Description desc) {
+        dumpDescription(desc, "[TestDescription]=", "  ");
+    }
+
+    private void dumpDescription(Description desc, String header, String indent) {
+        Log.v(TAG, indent + header + desc);
+
+        var children = desc.getChildren();
+        var childrenIndent = "  " + indent;
+        for (int i = 0; i < children.size(); i++) {
+            dumpDescription(children.get(i), "#" + i + ": ", childrenIndent);
+        }
+    }
+
+    static volatile BiConsumer<String, Throwable> sCriticalErrorHandler = null;
+
+    static void onCriticalError(@NonNull String message, @Nullable Throwable th) {
+        Log.e(TAG, "Critical error! " + message, th);
+        var handler = sCriticalErrorHandler;
+        if (handler == null) {
+            Log.e(TAG, "Ravenwood cannot continue. Killing self process.", th);
+            System.exit(1);
+        }
+        handler.accept(message, th);
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
deleted file mode 100644
index 478bead..0000000
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ /dev/null
@@ -1,199 +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 android.platform.test.ravenwood;
-
-import static org.junit.Assert.fail;
-
-import android.os.Bundle;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
-import android.platform.test.ravenwood.RavenwoodTestStats.Result;
-import android.util.Log;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.runner.Description;
-import org.junit.runners.model.TestClass;
-
-/**
- * Provide hook points created by {@link RavenwoodAwareTestRunner}.
- *
- * States are associated with each {@link RavenwoodAwareTestRunner} are stored in
- * {@link RavenwoodRunnerState}, rather than as members of {@link RavenwoodAwareTestRunner}.
- * See its javadoc for the reasons.
- *
- * All methods in this class must be called from the test main thread.
- */
-public class RavenwoodAwareTestRunnerHook {
-    private static final String TAG = RavenwoodAwareTestRunner.TAG;
-
-    private RavenwoodAwareTestRunnerHook() {
-    }
-
-    /**
-     * Called before any code starts. Internally it will only initialize the environment once.
-     */
-    public static void performGlobalInitialization() {
-        RavenwoodRuntimeEnvironmentController.globalInitOnce();
-    }
-
-    /**
-     * Called when a runner starts, before the inner runner gets a chance to run.
-     */
-    public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) {
-        Log.i(TAG, "onRunnerInitializing: testClass=" + testClass.getJavaClass()
-                + " runner=" + runner);
-
-        // This is needed to make AndroidJUnit4ClassRunner happy.
-        InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
-    }
-
-    /**
-     * Called when a whole test class is skipped.
-     */
-    public static void onClassSkipped(Description description) {
-        Log.i(TAG, "onClassSkipped: description=" + description);
-        RavenwoodTestStats.getInstance().onClassSkipped(description);
-    }
-
-    /**
-     * Called before the inner runner starts.
-     */
-    public static void onBeforeInnerRunnerStart(
-            RavenwoodAwareTestRunner runner, Description description) throws Throwable {
-        Log.v(TAG, "onBeforeInnerRunnerStart: description=" + description);
-
-        // Prepare the environment before the inner runner starts.
-        runner.mState.enterTestClass(description);
-    }
-
-    /**
-     * Called after the inner runner finished.
-     */
-    public static void onAfterInnerRunnerFinished(
-            RavenwoodAwareTestRunner runner, Description description) throws Throwable {
-        Log.v(TAG, "onAfterInnerRunnerFinished: description=" + description);
-
-        RavenwoodTestStats.getInstance().onClassFinished(description);
-        runner.mState.exitTestClass();
-    }
-
-    /**
-     * Called before a test / class.
-     *
-     * Return false if it should be skipped.
-     */
-    public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
-            Scope scope, Order order) throws Throwable {
-        Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order);
-
-        if (scope == Scope.Instance && order == Order.Outer) {
-            // Start of a test method.
-            runner.mState.enterTestMethod(description);
-        }
-
-        final var classDescription = runner.mState.getClassDescription();
-
-        // Class-level annotations are checked by the runner already, so we only check
-        // method-level annotations here.
-        if (scope == Scope.Instance && order == Order.Outer) {
-            if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(
-                    description, true)) {
-                RavenwoodTestStats.getInstance().onTestFinished(
-                        classDescription, description, Result.Skipped);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Called after a test / class.
-     *
-     * Return false if the exception should be ignored.
-     */
-    public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description,
-            Scope scope, Order order, Throwable th) {
-        Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
-
-        final var classDescription = runner.mState.getClassDescription();
-
-        if (scope == Scope.Instance && order == Order.Outer) {
-            // End of a test method.
-            runner.mState.exitTestMethod();
-            RavenwoodTestStats.getInstance().onTestFinished(classDescription, description,
-                    th == null ? Result.Passed : Result.Failed);
-        }
-
-        // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
-        if (RavenwoodRule.private$ravenwood().isRunningDisabledTests()
-                && scope == Scope.Instance && order == Order.Outer) {
-
-            boolean isTestEnabled = RavenwoodEnablementChecker.shouldEnableOnRavenwood(
-                    description, false);
-            if (th == null) {
-                // Test passed. Is the test method supposed to be enabled?
-                if (isTestEnabled) {
-                    // Enabled and didn't throw, okay.
-                    return true;
-                } else {
-                    // Disabled and didn't throw. We should report it.
-                    fail("Test wasn't included under Ravenwood, but it actually "
-                            + "passed under Ravenwood; consider updating annotations");
-                    return true; // unreachable.
-                }
-            } else {
-                // Test failed.
-                if (isTestEnabled) {
-                    // Enabled but failed. We should throw the exception.
-                    return true;
-                } else {
-                    // Disabled and failed. Expected. Don't throw.
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Called by {@link RavenwoodAwareTestRunner} to see if it should run a test class or not.
-     */
-    public static boolean shouldRunClassOnRavenwood(Class<?> clazz) {
-        return RavenwoodEnablementChecker.shouldRunClassOnRavenwood(clazz, true);
-    }
-
-    /**
-     * Called by RavenwoodRule.
-     */
-    public static void onRavenwoodRuleEnter(RavenwoodAwareTestRunner runner,
-            Description description, RavenwoodRule rule) throws Throwable {
-        Log.v(TAG, "onRavenwoodRuleEnter: description=" + description);
-
-        runner.mState.enterRavenwoodRule(rule);
-    }
-
-
-    /**
-     * Called by RavenwoodRule.
-     */
-    public static void onRavenwoodRuleExit(RavenwoodAwareTestRunner runner,
-            Description description, RavenwoodRule rule) throws Throwable {
-        Log.v(TAG, "onRavenwoodRuleExit: description=" + description);
-
-        runner.mState.exitRavenwoodRule(rule);
-    }
-}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigPrivate.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigPrivate.java
new file mode 100644
index 0000000..ffb642d
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigPrivate.java
@@ -0,0 +1,36 @@
+/*
+ * 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 android.annotation.Nullable;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Contains Ravenwood private APIs.
+ */
+public class RavenwoodConfigPrivate {
+    private RavenwoodConfigPrivate() {
+    }
+
+    /**
+     * Set a listener for onCriticalError(), for testing. If a listener is set, we won't call
+     * System.exit().
+     */
+    public static void setCriticalErrorHandler(@Nullable BiConsumer<String, Throwable> handler) {
+        RavenwoodAwareTestRunner.sCriticalErrorHandler = handler;
+    }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunNotifier.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunNotifier.java
new file mode 100644
index 0000000..6903035
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunNotifier.java
@@ -0,0 +1,227 @@
+/*
+ * 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 android.util.Log;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runner.notification.StoppedByUserException;
+import org.junit.runners.model.MultipleFailureException;
+
+import java.util.ArrayList;
+import java.util.Stack;
+
+/**
+ * A run notifier that wraps another notifier and provides the following features:
+ * - Handle a failure that happened before testStarted and testEnded (typically that means
+ *   it's from @BeforeClass or @AfterClass, or a @ClassRule) and deliver it as if
+ *   individual tests in the class reported it. This is for b/364395552.
+ *
+ * - Logging.
+ */
+class RavenwoodRunNotifier extends RunNotifier {
+    private final RunNotifier mRealNotifier;
+
+    private final Stack<Description> mSuiteStack = new Stack<>();
+    private Description mCurrentSuite = null;
+    private final ArrayList<Throwable> mOutOfTestFailures = new ArrayList<>();
+
+    private boolean mBeforeTest = true;
+    private boolean mAfterTest = false;
+
+    RavenwoodRunNotifier(RunNotifier realNotifier) {
+        mRealNotifier = realNotifier;
+    }
+
+    private boolean isInTest() {
+        return !mBeforeTest && !mAfterTest;
+    }
+
+    @Override
+    public void addListener(RunListener listener) {
+        mRealNotifier.addListener(listener);
+    }
+
+    @Override
+    public void removeListener(RunListener listener) {
+        mRealNotifier.removeListener(listener);
+    }
+
+    @Override
+    public void addFirstListener(RunListener listener) {
+        mRealNotifier.addFirstListener(listener);
+    }
+
+    @Override
+    public void fireTestRunStarted(Description description) {
+        Log.i(RavenwoodAwareTestRunner.TAG, "testRunStarted: " + description);
+        mRealNotifier.fireTestRunStarted(description);
+    }
+
+    @Override
+    public void fireTestRunFinished(Result result) {
+        Log.i(RavenwoodAwareTestRunner.TAG, "testRunFinished: "
+                + result.getRunCount() + ","
+                + result.getFailureCount() + ","
+                + result.getAssumptionFailureCount() + ","
+                + result.getIgnoreCount());
+        mRealNotifier.fireTestRunFinished(result);
+    }
+
+    @Override
+    public void fireTestSuiteStarted(Description description) {
+        Log.i(RavenwoodAwareTestRunner.TAG, "testSuiteStarted: " + description);
+        mRealNotifier.fireTestSuiteStarted(description);
+
+        mBeforeTest = true;
+        mAfterTest = false;
+
+        // Keep track of the current suite, needed if the outer test is a Suite,
+        // in which case its children are test classes. (not test methods)
+        mCurrentSuite = description;
+        mSuiteStack.push(description);
+
+        mOutOfTestFailures.clear();
+    }
+
+    @Override
+    public void fireTestSuiteFinished(Description description) {
+        Log.i(RavenwoodAwareTestRunner.TAG, "testSuiteFinished: " + description);
+        mRealNotifier.fireTestSuiteFinished(description);
+
+        maybeHandleOutOfTestFailures();
+
+        mBeforeTest = true;
+        mAfterTest = false;
+
+        // Restore the upper suite.
+        mSuiteStack.pop();
+        mCurrentSuite = mSuiteStack.size() == 0 ? null : mSuiteStack.peek();
+    }
+
+    @Override
+    public void fireTestStarted(Description description) throws StoppedByUserException {
+        Log.i(RavenwoodAwareTestRunner.TAG, "testStarted: " + description);
+        mRealNotifier.fireTestStarted(description);
+
+        mAfterTest = false;
+        mBeforeTest = false;
+    }
+
+    @Override
+    public void fireTestFailure(Failure failure) {
+        Log.i(RavenwoodAwareTestRunner.TAG, "testFailure: " + failure);
+
+        if (isInTest()) {
+            mRealNotifier.fireTestFailure(failure);
+        } else {
+            mOutOfTestFailures.add(failure.getException());
+        }
+    }
+
+    @Override
+    public void fireTestAssumptionFailed(Failure failure) {
+        Log.i(RavenwoodAwareTestRunner.TAG, "testAssumptionFailed: " + failure);
+
+        if (isInTest()) {
+            mRealNotifier.fireTestAssumptionFailed(failure);
+        } else {
+            mOutOfTestFailures.add(failure.getException());
+        }
+    }
+
+    @Override
+    public void fireTestIgnored(Description description) {
+        Log.i(RavenwoodAwareTestRunner.TAG, "testIgnored: " + description);
+        mRealNotifier.fireTestIgnored(description);
+    }
+
+    @Override
+    public void fireTestFinished(Description description) {
+        Log.i(RavenwoodAwareTestRunner.TAG, "testFinished: " + description);
+        mRealNotifier.fireTestFinished(description);
+
+        mAfterTest = true;
+    }
+
+    @Override
+    public void pleaseStop() {
+        Log.w(RavenwoodAwareTestRunner.TAG, "pleaseStop:");
+        mRealNotifier.pleaseStop();
+    }
+
+    /**
+     * At the end of each Suite, we handle failures happened out of test methods.
+     * (typically in @BeforeClass or @AfterClasses)
+     *
+     * This is to work around b/364395552.
+     */
+    private boolean maybeHandleOutOfTestFailures() {
+        if (mOutOfTestFailures.size() == 0) {
+            return false;
+        }
+        Throwable th;
+        if (mOutOfTestFailures.size() == 1) {
+            th = mOutOfTestFailures.get(0);
+        } else {
+            th = new MultipleFailureException(mOutOfTestFailures);
+        }
+        if (mBeforeTest) {
+            reportBeforeTestFailure(mCurrentSuite, th);
+            return true;
+        }
+        if (mAfterTest) {
+            reportAfterTestFailure(th);
+            return true;
+        }
+        return false;
+    }
+
+    public void reportBeforeTestFailure(Description suiteDesc, Throwable th) {
+        // If a failure happens befere running any tests, we'll need to pretend
+        // as if each test in the suite reported the failure, to work around b/364395552.
+        for (var child : suiteDesc.getChildren()) {
+            if (child.isSuite()) {
+                // If the chiil is still a "parent" -- a test class or a test suite
+                // -- propagate to its children.
+                mRealNotifier.fireTestSuiteStarted(child);
+                reportBeforeTestFailure(child, th);
+                mRealNotifier.fireTestSuiteFinished(child);
+            } else {
+                mRealNotifier.fireTestStarted(child);
+                Failure f = new Failure(child, th);
+                if (th instanceof AssumptionViolatedException) {
+                    mRealNotifier.fireTestAssumptionFailed(f);
+                } else {
+                    mRealNotifier.fireTestFailure(f);
+                }
+                mRealNotifier.fireTestFinished(child);
+            }
+        }
+    }
+
+    public void reportAfterTestFailure(Throwable th) {
+        // Unfortunately, there's no good way to report it, so kill the own process.
+        RavenwoodAwareTestRunner.onCriticalError(
+                "Failures detected in @AfterClass, which would be swallowed by tradefed",
+                th);
+    }
+}
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 03513ab..ec00e8f 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -20,8 +20,8 @@
 import static org.junit.Assert.fail;
 
 import android.annotation.Nullable;
+import android.util.Log;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.ravenwood.common.RavenwoodRuntimeException;
 
 import org.junit.ClassRule;
@@ -29,9 +29,7 @@
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 
-import java.io.IOException;
 import java.lang.reflect.Field;
-import java.util.WeakHashMap;
 
 /**
  * Used to store various states associated with the current test runner that's inly needed
@@ -45,10 +43,6 @@
 public final class RavenwoodRunnerState {
     private static final String TAG = "RavenwoodRunnerState";
 
-    @GuardedBy("sStates")
-    private static final WeakHashMap<RavenwoodAwareTestRunner, RavenwoodRunnerState> sStates =
-            new WeakHashMap<>();
-
     private final RavenwoodAwareTestRunner mRunner;
 
     /**
@@ -58,35 +52,65 @@
         mRunner = runner;
     }
 
-    private Description mClassDescription;
+    /**
+     * The RavenwoodConfig used to configure the current Ravenwood environment.
+     * This can either come from mConfig or mRule.
+     */
+    private RavenwoodConfig mCurrentConfig;
+    /**
+     * 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;
 
-    private RavenwoodConfig mCurrentConfig;
-    private RavenwoodRule mCurrentRule;
-    private boolean mHasRavenwoodRule;
-
-    public Description getClassDescription() {
-        return mClassDescription;
+    public RavenwoodConfig getConfig() {
+        return mCurrentConfig;
     }
 
-    public void enterTestClass(Description classDescription) throws IOException {
-        mClassDescription = classDescription;
+    public void enterTestRunner() {
+        Log.i(TAG, "enterTestRunner: " + mRunner);
 
-        mHasRavenwoodRule = hasRavenwoodRule(mRunner.getTestClass().getJavaClass());
-        mCurrentConfig = extractConfiguration(mRunner.getTestClass().getJavaClass());
+        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.");
+            }
+            mCurrentConfig = mConfig;
+        } else if (!mHasRavenwoodRule) {
+            // If no RavenwoodConfig and no RavenwoodRule, use a default config
+            mCurrentConfig = new RavenwoodConfig.Builder().build();
+        }
 
         if (mCurrentConfig != null) {
-            RavenwoodRuntimeEnvironmentController.init(mCurrentConfig);
+            RavenwoodRuntimeEnvironmentController.init(mRunner);
+        }
+    }
+
+    public void enterTestClass() {
+        Log.i(TAG, "enterTestClass: " + mRunner.mTestJavaClass.getName());
+
+        if (mCurrentConfig != null) {
+            RavenwoodRuntimeEnvironmentController.init(mRunner);
         }
     }
 
     public void exitTestClass() {
-        if (mCurrentConfig != null) {
-            try {
+        Log.i(TAG, "exitTestClass: " + mRunner.mTestJavaClass.getName());
+        try {
+            if (mCurrentConfig != null) {
                 RavenwoodRuntimeEnvironmentController.reset();
-            } finally {
-                mClassDescription = null;
             }
+        } finally {
+            mConfig = null;
+            mRule = null;
         }
     }
 
@@ -96,55 +120,40 @@
 
     public void exitTestMethod() {
         mMethodDescription = null;
+        RavenwoodRuntimeEnvironmentController.reinit();
     }
 
-    public void enterRavenwoodRule(RavenwoodRule rule) throws IOException {
+    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 (mCurrentConfig != null) {
-            fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
-                    + " Suggest migrating to RavenwoodConfig.");
-        }
-        if (mCurrentRule != null) {
+        if (mRule != null) {
             fail("Multiple nesting RavenwoodRule's are detected in the same class,"
                     + " which is not supported.");
         }
-        mCurrentRule = rule;
-        RavenwoodRuntimeEnvironmentController.init(rule.getConfiguration());
+        mRule = rule;
+        if (mCurrentConfig == null) {
+            mCurrentConfig = rule.getConfiguration();
+        }
+        RavenwoodRuntimeEnvironmentController.init(mRunner);
     }
 
     public void exitRavenwoodRule(RavenwoodRule rule) {
-        if (mCurrentRule != rule) {
-            return; // This happens if the rule did _not_ take effect somehow.
+        if (mRule != rule) {
+            fail("RavenwoodRule did not take effect.");
         }
-
-        try {
-            RavenwoodRuntimeEnvironmentController.reset();
-        } finally {
-            mCurrentRule = null;
-        }
+        mRule = null;
     }
 
     /**
      * @return a configuration from a test class, if any.
      */
     @Nullable
-    private RavenwoodConfig extractConfiguration(Class<?> testClass) {
+    private static RavenwoodConfig extractConfiguration(Class<?> testClass) {
         var field = findConfigurationField(testClass);
         if (field == null) {
-            if (mHasRavenwoodRule) {
-                // Should be handled by RavenwoodRule
-                return null;
-            }
-
-            // If no RavenwoodConfig and no RavenwoodRule, return a default config
-            return new RavenwoodConfig.Builder().build();
-        }
-        if (mHasRavenwoodRule) {
-            fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
-                    + " Suggest migrating to RavenwoodConfig.");
+            return null;
         }
 
         try {
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 c2806da..9002e40 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -137,7 +137,7 @@
         return res;
     }
 
-    private static RavenwoodConfig sConfig;
+    private static RavenwoodAwareTestRunner sRunner;
     private static RavenwoodSystemProperties sProps;
     private static boolean sInitialized = false;
 
@@ -171,6 +171,10 @@
         // Redirect stdout/stdin to liblog.
         RuntimeInit.redirectLogStreams();
 
+        // Touch some references early to ensure they're <clinit>'ed
+        Objects.requireNonNull(Build.TYPE);
+        Objects.requireNonNull(Build.VERSION.SDK);
+
         if (RAVENWOOD_VERBOSE_LOGGING) {
             RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
             try {
@@ -191,12 +195,19 @@
     /**
      * Initialize the environment.
      */
-    public static void init(RavenwoodConfig config) throws IOException {
+    public static void init(RavenwoodAwareTestRunner runner) {
         if (RAVENWOOD_VERBOSE_LOGGING) {
-            Log.i(TAG, "init() called here", new RuntimeException("STACKTRACE"));
+            Log.i(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
         }
+        if (sRunner == runner) {
+            return;
+        }
+        if (sRunner != null) {
+            reset();
+        }
+        sRunner = runner;
         try {
-            initInner(config);
+            initInner(runner.mState.getConfig());
         } catch (Exception th) {
             Log.e(TAG, "init() failed", th);
             reset();
@@ -205,10 +216,6 @@
     }
 
     private static void initInner(RavenwoodConfig config) throws IOException {
-        if (sConfig != null) {
-            throw new RavenwoodRuntimeException("Internal error: init() called without reset()");
-        }
-        sConfig = config;
         if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
             maybeThrowPendingUncaughtException(false);
             Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
@@ -216,7 +223,7 @@
 
         android.os.Process.init$ravenwood(config.mUid, config.mPid);
         sOriginalIdentityToken = Binder.clearCallingIdentity();
-        Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid));
+        reinit();
         setSystemProperties(config.mSystemProperties);
 
         ServiceManager.init$ravenwood();
@@ -269,9 +276,7 @@
         config.mInstContext = instContext;
         config.mTargetContext = targetContext;
 
-        final Supplier<Resources> systemResourcesLoader = () -> {
-            return config.mState.loadResources(null);
-        };
+        final Supplier<Resources> systemResourcesLoader = () -> config.mState.loadResources(null);
 
         config.mState.mSystemServerContext =
                 new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader);
@@ -288,10 +293,14 @@
                     RavenwoodRuntimeEnvironmentController::dumpStacks,
                     TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }
+    }
 
-        // Touch some references early to ensure they're <clinit>'ed
-        Objects.requireNonNull(Build.TYPE);
-        Objects.requireNonNull(Build.VERSION.SDK);
+    /**
+     * Partially re-initialize after each test method invocation
+     */
+    public static void reinit() {
+        var config = sRunner.mState.getConfig();
+        Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid));
     }
 
     /**
@@ -301,11 +310,11 @@
         if (RAVENWOOD_VERBOSE_LOGGING) {
             Log.i(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
         }
-        if (sConfig == null) {
+        if (sRunner == null) {
             throw new RavenwoodRuntimeException("Internal error: reset() already called");
         }
-        var config = sConfig;
-        sConfig = null;
+        var config = sRunner.mState.getConfig();
+        sRunner = null;
 
         if (ENABLE_TIMEOUT_STACKS) {
             sPendingTimeout.cancel(false);
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index b4b8715..016de8e 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -90,9 +90,7 @@
         // Create the "latest" symlink.
         Path symlink = Paths.get(tmpdir, basename + "latest.csv");
         try {
-            if (Files.exists(symlink)) {
-                Files.delete(symlink);
-            }
+            Files.deleteIfExists(symlink);
             Files.createSymbolicLink(symlink, Paths.get(mOutputFile.getName()));
 
         } catch (IOException e) {
diff --git a/ravenwood/junit-src/android/platform/test/annotations/RavenwoodTestRunnerInitializing.java b/ravenwood/junit-src/android/platform/test/annotations/RavenwoodTestRunnerInitializing.java
new file mode 100644
index 0000000..3bba27a
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/RavenwoodTestRunnerInitializing.java
@@ -0,0 +1,32 @@
+/*
+ * 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.annotations;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation similar to JUnit's BeforeClass, but this gets executed before
+ * the inner runner is instantiated, and only on Ravenwood.
+ * It can be used to initialize what's needed by the inner runner.
+ */
+@Target({METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RavenwoodTestRunnerInitializing {
+}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/internal/InnerRunner.java b/ravenwood/junit-src/android/platform/test/annotations/internal/InnerRunner.java
new file mode 100644
index 0000000..dde53a5
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/internal/InnerRunner.java
@@ -0,0 +1,32 @@
+/*
+ * 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.annotations.internal;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import org.junit.runner.Runner;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Inherited
+@Target({TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface InnerRunner {
+    Class<? extends Runner> value();
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
deleted file mode 100644
index 5ba972df..0000000
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ /dev/null
@@ -1,733 +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 android.platform.test.ravenwood;
-
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.isOnRavenwood;
-
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.Log;
-
-import org.junit.Assume;
-import org.junit.AssumptionViolatedException;
-import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runner.Result;
-import org.junit.runner.Runner;
-import org.junit.runner.manipulation.Filter;
-import org.junit.runner.manipulation.Filterable;
-import org.junit.runner.manipulation.InvalidOrderingException;
-import org.junit.runner.manipulation.NoTestsRemainException;
-import org.junit.runner.manipulation.Orderable;
-import org.junit.runner.manipulation.Orderer;
-import org.junit.runner.manipulation.Sortable;
-import org.junit.runner.manipulation.Sorter;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
-import org.junit.runner.notification.RunNotifier;
-import org.junit.runner.notification.StoppedByUserException;
-import org.junit.runners.BlockJUnit4ClassRunner;
-import org.junit.runners.Suite;
-import org.junit.runners.model.MultipleFailureException;
-import org.junit.runners.model.RunnerBuilder;
-import org.junit.runners.model.Statement;
-import org.junit.runners.model.TestClass;
-
-import java.lang.annotation.Annotation;
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Stack;
-import java.util.function.BiConsumer;
-
-/**
- * A test runner used for Ravenwood.
- *
- * It will delegate to another runner specified with {@link InnerRunner}
- * (default = {@link BlockJUnit4ClassRunner}) with the following features.
- * - Add a {@link RavenwoodAwareTestRunnerHook#onRunnerInitializing} hook, which is called before
- *   the inner runner gets a chance to run. This can be used to initialize stuff used by the
- *   inner runner.
- * - Add hook points, which are handed by RavenwoodAwareTestRunnerHook, with help from
- *   the four test rules such as {@link #sImplicitClassOuterRule}, which are also injected by
- *   the ravenizer tool.
- *
- * We use this runner to:
- * - Initialize the bare minimum environmnet just to be enough to make the actual test runners
- *   happy.
- * - Handle {@link android.platform.test.annotations.DisabledOnRavenwood}.
- *
- * This class is built such that it can also be used on a real device, but in that case
- * it will basically just delegate to the inner wrapper, and won't do anything special.
- * (no hooks, etc.)
- */
-public final class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable {
-    public static final String TAG = "Ravenwood";
-
-    @Inherited
-    @Target({TYPE})
-    @Retention(RetentionPolicy.RUNTIME)
-    public @interface InnerRunner {
-        Class<? extends Runner> value();
-    }
-
-    /**
-     * An annotation similar to JUnit's BeforeClass, but this gets executed before
-     * the inner runner is instantiated, and only on Ravenwood.
-     * It can be used to initialize what's needed by the inner runner.
-     */
-    @Target({METHOD})
-    @Retention(RetentionPolicy.RUNTIME)
-    public @interface RavenwoodTestRunnerInitializing {
-    }
-
-    /** Scope of a hook. */
-    public enum Scope {
-        Class,
-        Instance,
-    }
-
-    /** Order of a hook. */
-    public enum Order {
-        Outer,
-        Inner,
-    }
-
-    // The following four rule instances will be injected to tests by the Ravenizer tool.
-    private static class RavenwoodClassOuterRule implements TestRule {
-        @Override
-        public Statement apply(Statement base, Description description) {
-            return getCurrentRunner().wrapWithHooks(base, description, Scope.Class, Order.Outer);
-        }
-    }
-
-    private static class RavenwoodClassInnerRule implements TestRule {
-        @Override
-        public Statement apply(Statement base, Description description) {
-            return getCurrentRunner().wrapWithHooks(base, description, Scope.Class, Order.Inner);
-        }
-    }
-
-    private static class RavenwoodInstanceOuterRule implements TestRule {
-        @Override
-        public Statement apply(Statement base, Description description) {
-            return getCurrentRunner().wrapWithHooks(
-                    base, description, Scope.Instance, Order.Outer);
-        }
-    }
-
-    private static class RavenwoodInstanceInnerRule implements TestRule {
-        @Override
-        public Statement apply(Statement base, Description description) {
-            return getCurrentRunner().wrapWithHooks(
-                    base, description, Scope.Instance, Order.Inner);
-        }
-    }
-
-    public static final TestRule sImplicitClassOuterRule = new RavenwoodClassOuterRule();
-    public static final TestRule sImplicitClassInnerRule = new RavenwoodClassInnerRule();
-    public static final TestRule sImplicitInstOuterRule = new RavenwoodInstanceOuterRule();
-    public static final TestRule sImplicitInstInnerRule = new RavenwoodInstanceOuterRule();
-
-    public static final String IMPLICIT_CLASS_OUTER_RULE_NAME = "sImplicitClassOuterRule";
-    public static final String IMPLICIT_CLASS_INNER_RULE_NAME = "sImplicitClassInnerRule";
-    public static final String IMPLICIT_INST_OUTER_RULE_NAME = "sImplicitInstOuterRule";
-    public static final String IMPLICIT_INST_INNER_RULE_NAME = "sImplicitInstInnerRule";
-
-    /** Keeps track of the runner on the current thread. */
-    private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>();
-
-    static RavenwoodAwareTestRunner getCurrentRunner() {
-        var runner = sCurrentRunner.get();
-        if (runner == null) {
-            throw new RuntimeException("Current test runner not set!");
-        }
-        return runner;
-    }
-
-    private final Class<?> mTestJavaClass;
-    private TestClass mTestClass = null;
-    private Runner mRealRunner = null;
-    private Description mDescription = null;
-    private Throwable mExceptionInConstructor = null;
-    private boolean mRealRunnerTakesRunnerBuilder = false;
-
-    /**
-     * Stores internal states / methods associated with this runner that's only needed in
-     * junit-impl.
-     */
-    final RavenwoodRunnerState mState = new RavenwoodRunnerState(this);
-
-    private Error logAndFail(String message, Throwable exception) {
-        Log.e(TAG, message, exception);
-        throw new AssertionError(message, exception);
-    }
-
-    public TestClass getTestClass() {
-        return mTestClass;
-    }
-
-    /**
-     * Constructor.
-     */
-    public RavenwoodAwareTestRunner(Class<?> testClass) {
-        mTestJavaClass = testClass;
-        try {
-            performGlobalInitialization();
-
-            /*
-             * If the class has @DisabledOnRavenwood, then we'll delegate to
-             * ClassSkippingTestRunner, which simply skips it.
-             *
-             * We need to do it before instantiating TestClass for b/367694651.
-             */
-            if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
-                    testClass)) {
-                mRealRunner = new ClassSkippingTestRunner(testClass);
-                mDescription = mRealRunner.getDescription();
-                return;
-            }
-
-            mTestClass = new TestClass(testClass);
-
-            Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
-
-            onRunnerInitializing();
-
-            // Find the real runner.
-            final Class<? extends Runner> realRunnerClass;
-            final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class);
-            if (innerRunnerAnnotation != null) {
-                realRunnerClass = innerRunnerAnnotation.value();
-            } else {
-                // Default runner.
-                realRunnerClass = BlockJUnit4ClassRunner.class;
-            }
-
-            try {
-                Log.i(TAG, "Initializing the inner runner: " + realRunnerClass);
-
-                mRealRunner = instantiateRealRunner(realRunnerClass, testClass);
-                mDescription = mRealRunner.getDescription();
-
-            } catch (InstantiationException | IllegalAccessException
-                     | InvocationTargetException | NoSuchMethodException e) {
-                throw logAndFail("Failed to instantiate " + realRunnerClass, e);
-            }
-        } catch (Throwable th) {
-            // If we throw in the constructor, Tradefed may not report it and just ignore the class,
-            // so record it and throw it when the test actually started.
-            Log.e(TAG, "Fatal: Exception detected in constructor", th);
-            mExceptionInConstructor = new RuntimeException("Exception detected in constructor",
-                    th);
-            mDescription = Description.createTestDescription(testClass, "Constructor");
-
-            // This is for testing if tradefed is fixed.
-            if ("1".equals(System.getenv("RAVENWOOD_THROW_EXCEPTION_IN_TEST_RUNNER"))) {
-                throw th;
-            }
-        }
-    }
-
-    private Runner instantiateRealRunner(
-            Class<? extends Runner> realRunnerClass,
-            Class<?> testClass)
-            throws NoSuchMethodException, InvocationTargetException, InstantiationException,
-            IllegalAccessException {
-        try {
-            return realRunnerClass.getConstructor(Class.class).newInstance(testClass);
-        } catch (NoSuchMethodException e) {
-            var constructor = realRunnerClass.getConstructor(Class.class, RunnerBuilder.class);
-            mRealRunnerTakesRunnerBuilder = true;
-            return constructor.newInstance(testClass, new AllDefaultPossibilitiesBuilder());
-        }
-    }
-
-    private void performGlobalInitialization() {
-        if (!isOnRavenwood()) {
-            return;
-        }
-        RavenwoodAwareTestRunnerHook.performGlobalInitialization();
-    }
-
-    /**
-     * Run the bare minimum setup to initialize the wrapped runner.
-     */
-    // This method is called by the ctor, so never make it virtual.
-    private void onRunnerInitializing() {
-        if (!isOnRavenwood()) {
-            return;
-        }
-
-        RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass);
-
-        // Hook point to allow more customization.
-        runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
-    }
-
-    private void runAnnotatedMethodsOnRavenwood(Class<? extends Annotation> annotationClass,
-            Object instance) {
-        if (!isOnRavenwood()) {
-            return;
-        }
-        Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName());
-
-        for (var method : getTestClass().getAnnotatedMethods(annotationClass)) {
-            ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null);
-
-            var methodDesc = method.getDeclaringClass().getName() + "."
-                    + method.getMethod().toString();
-            try {
-                method.getMethod().invoke(instance);
-            } catch (IllegalAccessException | InvocationTargetException e) {
-                throw logAndFail("Caught exception while running method " + methodDesc, e);
-            }
-        }
-    }
-
-    @Override
-    public Description getDescription() {
-        return mDescription;
-    }
-
-    @Override
-    public void run(RunNotifier realNotifier) {
-        final RavenwoodRunNotifier notifier = new RavenwoodRunNotifier(realNotifier);
-
-        if (mRealRunner instanceof ClassSkippingTestRunner) {
-            mRealRunner.run(notifier);
-            RavenwoodAwareTestRunnerHook.onClassSkipped(getDescription());
-            return;
-        }
-
-        Log.v(TAG, "Starting " + mTestJavaClass.getCanonicalName());
-        if (RAVENWOOD_VERBOSE_LOGGING) {
-            dumpDescription(getDescription());
-        }
-
-        if (maybeReportExceptionFromConstructor(notifier)) {
-            return;
-        }
-
-        // TODO(b/365976974): handle nested classes better
-        final boolean skipRunnerHook =
-                mRealRunnerTakesRunnerBuilder && mRealRunner instanceof Suite;
-
-        sCurrentRunner.set(this);
-        try {
-            if (!skipRunnerHook) {
-                try {
-                    RavenwoodAwareTestRunnerHook.onBeforeInnerRunnerStart(
-                            this, getDescription());
-                } catch (Throwable th) {
-                    notifier.reportBeforeTestFailure(getDescription(), th);
-                    return;
-                }
-            }
-
-            // Delegate to the inner runner.
-            mRealRunner.run(notifier);
-        } finally {
-            sCurrentRunner.remove();
-
-            if (!skipRunnerHook) {
-                try {
-                    RavenwoodAwareTestRunnerHook.onAfterInnerRunnerFinished(
-                            this, getDescription());
-                } catch (Throwable th) {
-                    notifier.reportAfterTestFailure(th);
-                }
-            }
-        }
-    }
-
-    /** Throw the exception detected in the constructor, if any. */
-    private boolean maybeReportExceptionFromConstructor(RunNotifier notifier) {
-        if (mExceptionInConstructor == null) {
-            return false;
-        }
-        notifier.fireTestStarted(mDescription);
-        notifier.fireTestFailure(new Failure(mDescription, mExceptionInConstructor));
-        notifier.fireTestFinished(mDescription);
-
-        return true;
-    }
-
-    @Override
-    public void filter(Filter filter) throws NoTestsRemainException {
-        if (mRealRunner instanceof Filterable r) {
-            r.filter(filter);
-        }
-    }
-
-    @Override
-    public void order(Orderer orderer) throws InvalidOrderingException {
-        if (mRealRunner instanceof Orderable r) {
-            r.order(orderer);
-        }
-    }
-
-    @Override
-    public void sort(Sorter sorter) {
-        if (mRealRunner instanceof Sortable r) {
-            r.sort(sorter);
-        }
-    }
-
-    private Statement wrapWithHooks(Statement base, Description description, Scope scope,
-            Order order) {
-        if (!isOnRavenwood()) {
-            return base;
-        }
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                runWithHooks(description, scope, order, base);
-            }
-        };
-    }
-
-    private void runWithHooks(Description description, Scope scope, Order order, Runnable r)
-            throws Throwable {
-        runWithHooks(description, scope, order, new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                r.run();
-            }
-        });
-    }
-
-    private void runWithHooks(Description description, Scope scope, Order order, Statement s)
-            throws Throwable {
-        if (isOnRavenwood()) {
-            Assume.assumeTrue(
-                    RavenwoodAwareTestRunnerHook.onBefore(this, description, scope, order));
-        }
-        try {
-            s.evaluate();
-            if (isOnRavenwood()) {
-                RavenwoodAwareTestRunnerHook.onAfter(this, description, scope, order, null);
-            }
-        } catch (Throwable t) {
-            boolean shouldThrow = true;
-            if (isOnRavenwood()) {
-                shouldThrow = RavenwoodAwareTestRunnerHook.onAfter(
-                        this, description, scope, order, t);
-            }
-            if (shouldThrow) {
-                throw t;
-            }
-        }
-    }
-
-    /**
-     * A runner that simply skips a class. It still has to support {@link Filterable}
-     * because otherwise the result still says "SKIPPED" even when it's not included in the
-     * filter.
-     */
-    private static class ClassSkippingTestRunner extends Runner implements Filterable {
-        private final Description mDescription;
-        private boolean mFilteredOut;
-
-        ClassSkippingTestRunner(Class<?> testClass) {
-            mDescription = Description.createTestDescription(testClass, testClass.getSimpleName());
-            mFilteredOut = false;
-        }
-
-        @Override
-        public Description getDescription() {
-            return mDescription;
-        }
-
-        @Override
-        public void run(RunNotifier notifier) {
-            if (mFilteredOut) {
-                return;
-            }
-            notifier.fireTestSuiteStarted(mDescription);
-            notifier.fireTestIgnored(mDescription);
-            notifier.fireTestSuiteFinished(mDescription);
-        }
-
-        @Override
-        public void filter(Filter filter) throws NoTestsRemainException {
-            if (filter.shouldRun(mDescription)) {
-                mFilteredOut = false;
-            } else {
-                throw new NoTestsRemainException();
-            }
-        }
-    }
-
-    private void dumpDescription(Description desc) {
-        dumpDescription(desc, "[TestDescription]=", "  ");
-    }
-
-    private void dumpDescription(Description desc, String header, String indent) {
-        Log.v(TAG, indent + header + desc);
-
-        var children = desc.getChildren();
-        var childrenIndent = "  " + indent;
-        for (int i = 0; i < children.size(); i++) {
-            dumpDescription(children.get(i), "#" + i + ": ", childrenIndent);
-        }
-    }
-
-    /**
-     * A run notifier that wraps another notifier and provides the following features:
-     * - Handle a failure that happened before testStarted and testEnded (typically that means
-     *   it's from @BeforeClass or @AfterClass, or a @ClassRule) and deliver it as if
-     *   individual tests in the class reported it. This is for b/364395552.
-     *
-     * - Logging.
-     */
-    private class RavenwoodRunNotifier extends RunNotifier {
-        private final RunNotifier mRealNotifier;
-
-        private final Stack<Description> mSuiteStack = new Stack<>();
-        private Description mCurrentSuite = null;
-        private final ArrayList<Throwable> mOutOfTestFailures = new ArrayList<>();
-
-        private boolean mBeforeTest = true;
-        private boolean mAfterTest = false;
-
-        private RavenwoodRunNotifier(RunNotifier realNotifier) {
-            mRealNotifier = realNotifier;
-        }
-
-        private boolean isInTest() {
-            return !mBeforeTest && !mAfterTest;
-        }
-
-        @Override
-        public void addListener(RunListener listener) {
-            mRealNotifier.addListener(listener);
-        }
-
-        @Override
-        public void removeListener(RunListener listener) {
-            mRealNotifier.removeListener(listener);
-        }
-
-        @Override
-        public void addFirstListener(RunListener listener) {
-            mRealNotifier.addFirstListener(listener);
-        }
-
-        @Override
-        public void fireTestRunStarted(Description description) {
-            Log.i(TAG, "testRunStarted: " + description);
-            mRealNotifier.fireTestRunStarted(description);
-        }
-
-        @Override
-        public void fireTestRunFinished(Result result) {
-            Log.i(TAG, "testRunFinished: "
-                    + result.getRunCount() + ","
-                    + result.getFailureCount() + ","
-                    + result.getAssumptionFailureCount() + ","
-                    + result.getIgnoreCount());
-            mRealNotifier.fireTestRunFinished(result);
-        }
-
-        @Override
-        public void fireTestSuiteStarted(Description description) {
-            Log.i(TAG, "testSuiteStarted: " + description);
-            mRealNotifier.fireTestSuiteStarted(description);
-
-            mBeforeTest = true;
-            mAfterTest = false;
-
-            // Keep track of the current suite, needed if the outer test is a Suite,
-            // in which case its children are test classes. (not test methods)
-            mCurrentSuite = description;
-            mSuiteStack.push(description);
-
-            mOutOfTestFailures.clear();
-        }
-
-        @Override
-        public void fireTestSuiteFinished(Description description) {
-            Log.i(TAG, "testSuiteFinished: " + description);
-            mRealNotifier.fireTestSuiteFinished(description);
-
-            maybeHandleOutOfTestFailures();
-
-            mBeforeTest = true;
-            mAfterTest = false;
-
-            // Restore the upper suite.
-            mSuiteStack.pop();
-            mCurrentSuite = mSuiteStack.size() == 0 ? null : mSuiteStack.peek();
-        }
-
-        @Override
-        public void fireTestStarted(Description description) throws StoppedByUserException {
-            Log.i(TAG, "testStarted: " + description);
-            mRealNotifier.fireTestStarted(description);
-
-            mAfterTest = false;
-            mBeforeTest = false;
-        }
-
-        @Override
-        public void fireTestFailure(Failure failure) {
-            Log.i(TAG, "testFailure: " + failure);
-
-            if (isInTest()) {
-                mRealNotifier.fireTestFailure(failure);
-            } else {
-                mOutOfTestFailures.add(failure.getException());
-            }
-        }
-
-        @Override
-        public void fireTestAssumptionFailed(Failure failure) {
-            Log.i(TAG, "testAssumptionFailed: " + failure);
-
-            if (isInTest()) {
-                mRealNotifier.fireTestAssumptionFailed(failure);
-            } else {
-                mOutOfTestFailures.add(failure.getException());
-            }
-        }
-
-        @Override
-        public void fireTestIgnored(Description description) {
-            Log.i(TAG, "testIgnored: " + description);
-            mRealNotifier.fireTestIgnored(description);
-        }
-
-        @Override
-        public void fireTestFinished(Description description) {
-            Log.i(TAG, "testFinished: " + description);
-            mRealNotifier.fireTestFinished(description);
-
-            mAfterTest = true;
-        }
-
-        @Override
-        public void pleaseStop() {
-            Log.w(TAG, "pleaseStop:");
-            mRealNotifier.pleaseStop();
-        }
-
-        /**
-         * At the end of each Suite, we handle failures happened out of test methods.
-         * (typically in @BeforeClass or @AfterClasses)
-         *
-         * This is to work around b/364395552.
-         */
-        private boolean maybeHandleOutOfTestFailures() {
-            if (mOutOfTestFailures.size() == 0) {
-                return false;
-            }
-            Throwable th;
-            if (mOutOfTestFailures.size() == 1) {
-                th = mOutOfTestFailures.get(0);
-            } else {
-                th = new MultipleFailureException(mOutOfTestFailures);
-            }
-            if (mBeforeTest) {
-                reportBeforeTestFailure(mCurrentSuite, th);
-                return true;
-            }
-            if (mAfterTest) {
-                reportAfterTestFailure(th);
-                return true;
-            }
-            return false;
-        }
-
-        public void reportBeforeTestFailure(Description suiteDesc, Throwable th) {
-            // If a failure happens befere running any tests, we'll need to pretend
-            // as if each test in the suite reported the failure, to work around b/364395552.
-            for (var child : suiteDesc.getChildren()) {
-                if (child.isSuite()) {
-                    // If the chiil is still a "parent" -- a test class or a test suite
-                    // -- propagate to its children.
-                    mRealNotifier.fireTestSuiteStarted(child);
-                    reportBeforeTestFailure(child, th);
-                    mRealNotifier.fireTestSuiteFinished(child);
-                } else {
-                    mRealNotifier.fireTestStarted(child);
-                    Failure f = new Failure(child, th);
-                    if (th instanceof AssumptionViolatedException) {
-                        mRealNotifier.fireTestAssumptionFailed(f);
-                    } else {
-                        mRealNotifier.fireTestFailure(f);
-                    }
-                    mRealNotifier.fireTestFinished(child);
-                }
-            }
-        }
-
-        public void reportAfterTestFailure(Throwable th) {
-            // Unfortunately, there's no good way to report it, so kill the own process.
-            onCriticalError(
-                    "Failures detected in @AfterClass, which would be swallowed by tradefed",
-                    th);
-        }
-    }
-
-    private static volatile BiConsumer<String, Throwable> sCriticalErrorHanler;
-
-    private void onCriticalError(@NonNull String message, @Nullable Throwable th) {
-        Log.e(TAG, "Critical error! " + message, th);
-        var handler = sCriticalErrorHanler;
-        if (handler == null) {
-            handler = sDefaultCriticalErrorHandler;
-        }
-        handler.accept(message, th);
-    }
-
-    private static BiConsumer<String, Throwable> sDefaultCriticalErrorHandler = (message, th) -> {
-        Log.e(TAG, "Ravenwood cannot continue. Killing self process.", th);
-        System.exit(1);
-    };
-
-    /**
-     * Contains Ravenwood private APIs.
-     */
-    public static class RavenwoodPrivate {
-        private RavenwoodPrivate() {
-        }
-
-        /**
-         * Set a listener for onCriticalError(), for testing. If a listener is set, we won't call
-         * System.exit().
-         */
-        public void setCriticalErrorHandler(
-                @Nullable BiConsumer<String, Throwable> handler) {
-            sCriticalErrorHanler = handler;
-        }
-    }
-
-    private static final RavenwoodPrivate sRavenwoodPrivate = new RavenwoodPrivate();
-
-    public static RavenwoodPrivate private$ravenwood() {
-        return sRavenwoodPrivate;
-    }
-}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
new file mode 100644
index 0000000..31a1416
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerBase.java
@@ -0,0 +1,101 @@
+/*
+ * 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 android.platform.test.annotations.internal.InnerRunner;
+import android.util.Log;
+
+import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.InvalidOrderingException;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.manipulation.Orderable;
+import org.junit.runner.manipulation.Orderer;
+import org.junit.runner.manipulation.Sortable;
+import org.junit.runner.manipulation.Sorter;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.RunnerBuilder;
+import org.junit.runners.model.TestClass;
+
+abstract class RavenwoodAwareTestRunnerBase extends Runner implements Filterable, Orderable {
+    private static final String TAG = "Ravenwood";
+
+    boolean mRealRunnerTakesRunnerBuilder = false;
+
+    abstract Runner getRealRunner();
+
+    final Runner instantiateRealRunner(TestClass testClass) {
+        // Find the real runner.
+        final Class<? extends Runner> runnerClass;
+        final InnerRunner innerRunnerAnnotation = testClass.getAnnotation(InnerRunner.class);
+        if (innerRunnerAnnotation != null) {
+            runnerClass = innerRunnerAnnotation.value();
+        } else {
+            // Default runner.
+            runnerClass = BlockJUnit4ClassRunner.class;
+        }
+
+        try {
+            Log.i(TAG, "Initializing the inner runner: " + runnerClass);
+            try {
+                return runnerClass.getConstructor(Class.class)
+                        .newInstance(testClass.getJavaClass());
+            } catch (NoSuchMethodException e) {
+                var constructor = runnerClass.getConstructor(Class.class, RunnerBuilder.class);
+                mRealRunnerTakesRunnerBuilder = true;
+                return constructor.newInstance(
+                        testClass.getJavaClass(), new AllDefaultPossibilitiesBuilder());
+            }
+        } catch (ReflectiveOperationException e) {
+            throw logAndFail("Failed to instantiate " + runnerClass, e);
+        }
+    }
+
+    final Error logAndFail(String message, Throwable exception) {
+        Log.e(TAG, message, exception);
+        return new AssertionError(message, exception);
+    }
+
+    @Override
+    public final Description getDescription() {
+        return getRealRunner().getDescription();
+    }
+
+    @Override
+    public final void filter(Filter filter) throws NoTestsRemainException {
+        if (getRealRunner() instanceof Filterable r) {
+            r.filter(filter);
+        }
+    }
+
+    @Override
+    public final void order(Orderer orderer) throws InvalidOrderingException {
+        if (getRealRunner() instanceof Orderable r) {
+            r.order(orderer);
+        }
+    }
+
+    @Override
+    public final void sort(Sorter sorter) {
+        if (getRealRunner() instanceof Sortable r) {
+            r.sort(sorter);
+        }
+    }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 93a6806..3d6ac0f 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -24,6 +24,8 @@
 import android.content.Context;
 import android.platform.test.annotations.DisabledOnRavenwood;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import com.android.ravenwood.common.RavenwoodCommonUtils;
 
 import org.junit.rules.TestRule;
@@ -219,8 +221,7 @@
      */
     @Deprecated
     public Context getContext() {
-        return Objects.requireNonNull(mConfiguration.mInstContext,
-                "Context is only available during @Test execution");
+        return InstrumentationRegistry.getInstrumentation().getContext();
     }
 
     /**
@@ -230,8 +231,7 @@
      */
     @Deprecated
     public Instrumentation getInstrumentation() {
-        return Objects.requireNonNull(mConfiguration.mInstrumentation,
-                "Instrumentation is only available during @Test execution");
+        return InstrumentationRegistry.getInstrumentation();
     }
 
     @Override
@@ -242,15 +242,11 @@
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                RavenwoodAwareTestRunnerHook.onRavenwoodRuleEnter(
-                        RavenwoodAwareTestRunner.getCurrentRunner(), description,
-                        RavenwoodRule.this);
+                RavenwoodAwareTestRunner.onRavenwoodRuleEnter(description, RavenwoodRule.this);
                 try {
                     base.evaluate();
                 } finally {
-                    RavenwoodAwareTestRunnerHook.onRavenwoodRuleExit(
-                            RavenwoodAwareTestRunner.getCurrentRunner(), description,
-                            RavenwoodRule.this);
+                    RavenwoodAwareTestRunner.onRavenwoodRuleExit(description, RavenwoodRule.this);
                 }
             }
         };
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
new file mode 100644
index 0000000..b4b75178
--- /dev/null
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+/**
+ * A simple pass-through runner that just delegates to the inner runner without doing
+ * anything special (no hooks, etc.).
+ *
+ * This is only used when a real device-side test has Ravenizer enabled.
+ */
+public class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase {
+    private static final String TAG = "Ravenwood";
+
+    private static class NopRule implements TestRule {
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return base;
+        }
+    }
+
+    public static final TestRule sImplicitClassOuterRule = new NopRule();
+    public static final TestRule sImplicitClassInnerRule = sImplicitClassOuterRule;
+    public static final TestRule sImplicitInstOuterRule = sImplicitClassOuterRule;
+    public static final TestRule sImplicitInstInnerRule = sImplicitClassOuterRule;
+
+    private final Runner mRealRunner;
+
+    public RavenwoodAwareTestRunner(Class<?> clazz) {
+        Log.v(TAG, "RavenwoodAwareTestRunner starting for " + clazz.getCanonicalName());
+        mRealRunner = instantiateRealRunner(new TestClass(clazz));
+    }
+
+    @Override
+    Runner getRealRunner() {
+        return mRealRunner;
+    }
+
+    @Override
+    public void run(RunNotifier notifier) {
+        mRealRunner.run(notifier);
+    }
+
+    static void onRavenwoodRuleEnter(Description description, RavenwoodRule rule) {
+    }
+
+    static void onRavenwoodRuleExit(Description description, RavenwoodRule rule) {
+    }
+}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
deleted file mode 100644
index aa8c299..0000000
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ /dev/null
@@ -1,101 +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 android.platform.test.ravenwood;
-
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Order;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.Scope;
-
-import org.junit.runner.Description;
-import org.junit.runners.model.TestClass;
-
-/**
- * Provide hook points created by {@link RavenwoodAwareTestRunner}. This is a version
- * that's used on a device side test.
- *
- * All methods are no-op in real device tests.
- *
- * TODO: Use some kind of factory to provide different implementation for the device test
- * and the ravenwood test.
- */
-public class RavenwoodAwareTestRunnerHook {
-    private RavenwoodAwareTestRunnerHook() {
-    }
-
-    /**
-     * Called before any code starts. Internally it will only initialize the environment once.
-     */
-    public static void performGlobalInitialization() {
-    }
-
-    /**
-     * Called when a runner starts, before the inner runner gets a chance to run.
-     */
-    public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) {
-    }
-
-    /**
-     * Called when a whole test class is skipped.
-     */
-    public static void onClassSkipped(Description description) {
-    }
-
-    /**
-     * Called before the inner runner starts.
-     */
-    public static void onBeforeInnerRunnerStart(
-            RavenwoodAwareTestRunner runner, Description description) throws Throwable {
-    }
-
-    /**
-     * Called after the inner runner finished.
-     */
-    public static void onAfterInnerRunnerFinished(
-            RavenwoodAwareTestRunner runner, Description description) throws Throwable {
-    }
-
-    /**
-     * Called before a test / class.
-     *
-     * Return false if it should be skipped.
-     */
-    public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description,
-            Scope scope, Order order) throws Throwable {
-        return true;
-    }
-
-    public static void onRavenwoodRuleEnter(RavenwoodAwareTestRunner runner,
-            Description description, RavenwoodRule rule) throws Throwable {
-    }
-
-    public static void onRavenwoodRuleExit(RavenwoodAwareTestRunner runner,
-            Description description, RavenwoodRule rule) throws Throwable {
-    }
-
-
-    /**
-     * Called after a test / class.
-     *
-     * Return false if the exception should be ignored.
-     */
-    public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description,
-            Scope scope, Order order, Throwable th) {
-        return true;
-    }
-
-    public static boolean shouldRunClassOnRavenwood(Class<?> clazz) {
-        return true;
-    }
-}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
index 43a28ba..7d3d8b9 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
@@ -15,7 +15,7 @@
  */
 package android.platform.test.ravenwood;
 
-/** Stub class. The actual implementaetion is in junit-impl-src. */
+/** Stub class. The actual implementation is in junit-impl-src. */
 public class RavenwoodConfigState {
     public RavenwoodConfigState(RavenwoodConfig config) {
     }
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
deleted file mode 100644
index 83cbc52..0000000
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ /dev/null
@@ -1,22 +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 android.platform.test.ravenwood;
-
-/** Stub class. The actual implementaetion is in junit-impl-src. */
-public class RavenwoodRunnerState {
-    public RavenwoodRunnerState(RavenwoodAwareTestRunner runner) {
-    }
-}
diff --git a/ravenwood/scripts/ravenwood-test-summary b/ravenwood/scripts/ravenwood-test-summary
new file mode 100755
index 0000000..602fbff
--- /dev/null
+++ b/ravenwood/scripts/ravenwood-test-summary
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+'''
+Print the latest Ravenwood test execution summary
+
+Usage: /ravenwood-test-summary
+
+Example output:
+Module                                                         Passed   Failed  Skipped
+android.test.mock.ravenwood.tests                                   2        0        0
+CarLibHostUnitTest                                                565        0        7
+CarServiceHostUnitTest                                            364        0        0
+CtsAccountManagerTestCasesRavenwood                                 4        0        0
+CtsAppTestCasesRavenwood                                           21        0        0
+
+Description:
+This script finds all the test execution result from /tmp/Ravenwood-stats*,
+and shows per-module summary.
+'''
+
+import csv
+import glob
+import sys
+
+# Find the latest stats files.
+stats_files = glob.glob('/tmp/Ravenwood-stats_*_latest.csv')
+
+if len(stats_files) == 0:
+    print("No log files found.", file=sys.stderr)
+    exit(1)
+
+
+def parse_stats(file, result):
+    module = '(unknwon)'
+    passed = 0
+    failed = 0
+    skipped = 0
+    with open(file) as csvfile:
+        reader = csv.reader(csvfile, delimiter=',')
+
+
+        for i, row in enumerate(reader):
+            if i == 0: continue # Skip header line
+            module = row[0]
+            passed += int(row[3])
+            failed += int(row[4])
+            skipped += int(row[5])
+
+    result[module] = (passed, failed, skipped)
+
+
+result = {}
+
+for file in stats_files:
+    parse_stats(file, result)
+
+print('%-60s %8s %8s %8s' % ("Module", "Passed", "Failed", "Skipped"))
+
+for module in sorted(result.keys(), key=str.casefold):
+    r = result[module]
+    print('%-60s %8d %8d %8d' % (module, r[0], r[1], r[2]))
diff --git a/ravenwood/scripts/update-test-mapping.sh b/ravenwood/scripts/update-test-mapping.sh
index e478b50..ab37baf 100755
--- a/ravenwood/scripts/update-test-mapping.sh
+++ b/ravenwood/scripts/update-test-mapping.sh
@@ -23,6 +23,14 @@
 # Tests that shouldn't be in presubmit.
 EXEMPT='^(SystemUiRavenTests)$'
 
+is_car() {
+    local module="$1"
+
+    # If the module name starts with "Car", then it's a test for "Car".
+    [[ "$module" =~ ^Car ]]
+    return $?
+}
+
 main() {
     local script_name="${0##*/}"
     local script_dir="${0%/*}"
@@ -62,6 +70,10 @@
             fi
             echo "    {"
             echo "      \"name\": \"${tests[$i]}\","
+            if is_car "${tests[$i]}"; then
+                echo '      "keywords": ["automotive_code_coverage"],'
+            fi
+
             echo "      \"host\": true"
             echo "    }$comma"
 
diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
index d7f4b3e..ce0033d 100644
--- a/ravenwood/tests/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -31,9 +31,8 @@
     ],
 }
 
-android_ravenwood_test {
-    name: "RavenwoodBivalentTest",
-
+java_defaults {
+    name: "ravenwood-bivalent-defaults",
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.ext.junit",
@@ -51,15 +50,11 @@
     jni_libs: [
         "libravenwoodbivalenttest_jni",
     ],
-    auto_gen_config: true,
 }
 
-android_test {
-    name: "RavenwoodBivalentTest_device",
-
-    srcs: [
-        "test/**/*.java",
-    ],
+java_defaults {
+    name: "ravenwood-bivalent-device-defaults",
+    defaults: ["ravenwood-bivalent-defaults"],
     // TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
     exclude_srcs: [
         "test/**/ravenizer/*.java",
@@ -67,23 +62,32 @@
     static_libs: [
         "junit",
         "truth",
-
-        "androidx.annotation_annotation",
-        "androidx.test.ext.junit",
-        "androidx.test.rules",
-
-        "junit-params",
-        "platform-parametric-runner-lib",
-
         "ravenwood-junit",
     ],
-    jni_libs: [
-        "libravenwoodbivalenttest_jni",
-    ],
     test_suites: [
         "device-tests",
     ],
     optimize: {
         enabled: false,
     },
+    test_config_template: "AndroidTestTemplate.xml",
+}
+
+android_ravenwood_test {
+    name: "RavenwoodBivalentTest",
+    defaults: ["ravenwood-bivalent-defaults"],
+    auto_gen_config: true,
+}
+
+android_test {
+    name: "RavenwoodBivalentTest_device",
+    defaults: ["ravenwood-bivalent-device-defaults"],
+}
+
+android_test {
+    name: "RavenwoodBivalentTest_device_ravenizer",
+    defaults: ["ravenwood-bivalent-device-defaults"],
+    ravenizer: {
+        enabled: true,
+    },
 }
diff --git a/ravenwood/tests/bivalenttest/AndroidTest.xml b/ravenwood/tests/bivalenttest/AndroidTestTemplate.xml
similarity index 94%
rename from ravenwood/tests/bivalenttest/AndroidTest.xml
rename to ravenwood/tests/bivalenttest/AndroidTestTemplate.xml
index 9e5dd11..8f1a92c 100644
--- a/ravenwood/tests/bivalenttest/AndroidTest.xml
+++ b/ravenwood/tests/bivalenttest/AndroidTestTemplate.xml
@@ -19,7 +19,7 @@
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="RavenwoodBivalentTest_device.apk" />
+        <option name="test-file-name" value="{MODULE}.apk"/>
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
index d7c2c6c..4e21f86 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodAwareTestRunnerTest.java
@@ -16,12 +16,15 @@
 package com.android.ravenwoodtest.bivalenttest.ravenizer;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 
 import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Log;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
 
@@ -30,6 +33,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 /**
  * Make sure RavenwoodAwareTestRunnerTest properly delegates to the original runner,
  * and also run the special annotated methods.
@@ -61,13 +66,22 @@
         sCallTracker.incrementMethodCallCount();
     }
 
+    public RavenwoodAwareTestRunnerTest() {
+        // Make sure the environment is already initialized when the constructor is called
+        assertNotNull(InstrumentationRegistry.getInstrumentation());
+    }
+
     @Test
     public void test1() {
         sCallTracker.incrementMethodCallCount();
     }
 
+    public static List<String> testParams() {
+        return List.of("foo", "bar");
+    }
+
     @Test
-    @Parameters({"foo", "bar"})
+    @Parameters(method = "testParams")
     public void testWithParams(String arg) {
         sCallTracker.incrementMethodCallCount();
     }
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
index 9d878f4..77a807d 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
@@ -16,7 +16,7 @@
 package com.android.ravenwoodtest.bivalenttest.ravenizer;
 
 import android.platform.test.annotations.NoRavenizer;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
 
 import org.junit.Test;
 
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
index c77841b..e6e617b 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsReallyDisabledTest.java
@@ -18,7 +18,7 @@
 import static org.junit.Assert.fail;
 
 import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Log;
 
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
index ea1a29d..ef18c82 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodRunDisabledTestsTest.java
@@ -18,7 +18,7 @@
 import static org.junit.Assert.fail;
 
 import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.Log;
 
diff --git a/ravenwood/tests/coretest/Android.bp b/ravenwood/tests/coretest/Android.bp
index 412744e..9dd7cc6 100644
--- a/ravenwood/tests/coretest/Android.bp
+++ b/ravenwood/tests/coretest/Android.bp
@@ -16,11 +16,14 @@
         "androidx.test.rules",
         "junit-params",
         "platform-parametric-runner-lib",
-        "truth",
 
         // This library should be removed by Ravenizer
         "mockito-target-minus-junit4",
     ],
+    libs: [
+        // We access internal private classes
+        "ravenwood-junit-impl",
+    ],
     srcs: [
         "test/**/*.java",
         "test/**/*.kt",
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
index bd01313..6720e45 100644
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java
@@ -337,9 +337,9 @@
     @Expected("""
     testRunStarted: classes
     testSuiteStarted: classes
-    testStarted: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ExceptionFromInnerRunnerConstructorTest)
-    testFailure: Exception detected in constructor
-    testFinished: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ExceptionFromInnerRunnerConstructorTest)
+    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ExceptionFromInnerRunnerConstructorTest)
+    testFailure: Failed to instantiate class androidx.test.ext.junit.runners.AndroidJUnit4
+    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ExceptionFromInnerRunnerConstructorTest)
     testSuiteFinished: classes
     testRunFinished: 1,1,0,0
     """)
@@ -439,9 +439,9 @@
     @Expected("""
     testRunStarted: classes
     testSuiteStarted: classes
-    testStarted: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BrokenRunnerTest)
-    testFailure: Exception detected in constructor
-    testFinished: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BrokenRunnerTest)
+    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BrokenRunnerTest)
+    testFailure: Failed to instantiate class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BrokenTestRunner
+    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BrokenRunnerTest)
     testSuiteFinished: classes
     testRunFinished: 1,1,0,0
     """)
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
index 73ea64f..02d1073 100644
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
@@ -106,17 +106,11 @@
     @Expected("""
     testRunStarted: classes
     testSuiteStarted: classes
-    testStarted: testMethod1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
+    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
     testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest.sConfig expected to be public static
-    testFinished: testMethod1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
-    testStarted: testMethod2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
-    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest.sConfig expected to be public static
-    testFinished: testMethod2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
-    testStarted: testMethod3(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
-    testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest.sConfig expected to be public static
-    testFinished: testMethod3(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
+    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
     testSuiteFinished: classes
-    testRunFinished: 3,3,0,0
+    testRunFinished: 1,1,0,0
     """)
     // CHECKSTYLE:ON
     public static class ErrorMustBeReportedFromEachTest {
@@ -145,9 +139,9 @@
     @Expected("""
     testRunStarted: classes
     testSuiteStarted: classes
-    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
+    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
     testFailure: Class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.DuplicateConfigTest has multiple fields with @RavenwoodConfig.Config
-    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
+    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
     testSuiteFinished: classes
     testRunFinished: 1,1,0,0
     """)
@@ -175,9 +169,9 @@
     @Expected("""
     testRunStarted: classes
     testSuiteStarted: classes
-    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
+    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
     testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest.sConfig expected to be public static
-    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
+    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
     testSuiteFinished: classes
     testRunFinished: 1,1,0,0
     """)
@@ -201,9 +195,9 @@
     @Expected("""
     testRunStarted: classes
     testSuiteStarted: classes
-    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
+    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
     testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest.sConfig expected to be public static
-    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
+    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
     testSuiteFinished: classes
     testRunFinished: 1,1,0,0
     """)
@@ -227,9 +221,9 @@
     @Expected("""
     testRunStarted: classes
     testSuiteStarted: classes
-    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
+    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: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
+    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
     testSuiteFinished: classes
     testRunFinished: 1,1,0,0
     """)
@@ -282,9 +276,9 @@
     @Expected("""
     testRunStarted: classes
     testSuiteStarted: classes
-    testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
+    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
     testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
-    testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
+    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
     testSuiteFinished: classes
     testRunFinished: 1,1,0,0
     """)
@@ -311,9 +305,9 @@
     @Expected("""
     testRunStarted: classes
     testSuiteStarted: classes
-    testStarted: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
-    testFailure: Exception detected in constructor
-    testFinished: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
+    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
     """)
@@ -400,9 +394,9 @@
     @Expected("""
     testRunStarted: classes
     testSuiteStarted: classes
-    testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
+    testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
     testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
-    testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
+    testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
     testSuiteFinished: classes
     testRunFinished: 1,1,0,0
     """)
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
index 9a6934b..f7a2198 100644
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
@@ -20,6 +20,7 @@
 
 import android.platform.test.annotations.NoRavenizer;
 import android.platform.test.ravenwood.RavenwoodAwareTestRunner;
+import android.platform.test.ravenwood.RavenwoodConfigPrivate;
 import android.util.Log;
 
 import junitparams.JUnitParamsRunner;
@@ -137,15 +138,14 @@
         // Set a listener to critical errors. This will also prevent
         // {@link RavenwoodAwareTestRunner} from calling System.exit() when there's
         // a critical error.
-        RavenwoodAwareTestRunner.private$ravenwood().setCriticalErrorHandler(
-                listener.sCriticalErrorListener);
+        RavenwoodConfigPrivate.setCriticalErrorHandler(listener.sCriticalErrorListener);
 
         try {
             // Run the test class.
             junitCore.run(testClazz);
         } finally {
             // Clear the critical error listener.
-            RavenwoodAwareTestRunner.private$ravenwood().setCriticalErrorHandler(null);
+            RavenwoodConfigPrivate.setCriticalErrorHandler(null);
         }
 
         // Check the result.
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 9c86389..a26fe66 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -359,3 +359,6 @@
 com.android.server.SystemServiceManager
 
 com.android.server.utils.TimingsTraceAndSlog
+
+android.os.IpcDataCache
+android.app.PropertyInvalidatedCache
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
index 37a7975..6092fcc 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -15,6 +15,7 @@
  */
 package com.android.platform.test.ravenwood.ravenizer
 
+import android.platform.test.annotations.internal.InnerRunner
 import android.platform.test.annotations.NoRavenizer
 import android.platform.test.ravenwood.RavenwoodAwareTestRunner
 import com.android.hoststubgen.asm.ClassNodes
@@ -39,7 +40,7 @@
 val ruleAnotType = TypeHolder(org.junit.Rule::class.java)
 val classRuleAnotType = TypeHolder(org.junit.ClassRule::class.java)
 val runWithAnotType = TypeHolder(RunWith::class.java)
-val innerRunnerAnotType = TypeHolder(RavenwoodAwareTestRunner.InnerRunner::class.java)
+val innerRunnerAnotType = TypeHolder(InnerRunner::class.java)
 val noRavenizerAnotType = TypeHolder(NoRavenizer::class.java)
 
 val testRuleType = TypeHolder(TestRule::class.java)
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
index cf6d6f6..81fe3da 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
@@ -15,7 +15,6 @@
  */
 package com.android.platform.test.ravenwood.ravenizer.adapter
 
-import android.platform.test.ravenwood.RavenwoodAwareTestRunner
 import com.android.hoststubgen.ClassParseException
 import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
 import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
@@ -28,8 +27,8 @@
 import com.android.hoststubgen.visitors.OPCODE_VERSION
 import com.android.platform.test.ravenwood.ravenizer.RavenizerInternalException
 import com.android.platform.test.ravenwood.ravenizer.classRuleAnotType
-import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
 import com.android.platform.test.ravenwood.ravenizer.innerRunnerAnotType
+import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
 import com.android.platform.test.ravenwood.ravenizer.noRavenizerAnotType
 import com.android.platform.test.ravenwood.ravenizer.ravenwoodTestRunnerType
 import com.android.platform.test.ravenwood.ravenizer.ruleAnotType
@@ -50,7 +49,7 @@
  * Class visitor to update the RunWith and inject some necessary rules.
  *
  * - Change the @RunWith(RavenwoodAwareTestRunner.class).
- * - If the original class has a @RunWith(...), then change it to an @OrigRunWith(...).
+ * - If the original class has a @RunWith(...), then change it to an @InnerRunner(...).
  * - Add RavenwoodAwareTestRunner's member rules as junit rules.
  * - Update the order of the existing JUnit rules to make sure they don't use the MIN or MAX.
  */
@@ -146,7 +145,7 @@
 
     /**
      * Inject `@RunWith(RavenwoodAwareTestRunner.class)`. If the class already has
-     * a `@RunWith`, then change it to add a `@OrigRunWith`.
+     * a `@RunWith`, then change it to add a `@InnerRunner`.
      */
     private fun injectRunWithAnnotation() {
         // Extract the original RunWith annotation and its value.
@@ -172,7 +171,7 @@
                         + " in class ${classInternalName.toHumanReadableClassName()}")
             }
 
-            // Inject an @OrigRunWith.
+            // Inject an @InnerRunner.
             visitAnnotation(innerRunnerAnotType.desc, true)!!.let { av ->
                 av.visit("value", runWithClass)
                 av.visitEnd()
@@ -302,7 +301,7 @@
         override fun visitCode() {
             visitFieldInsn(Opcodes.GETSTATIC,
                 ravenwoodTestRunnerType.internlName,
-                RavenwoodAwareTestRunner.IMPLICIT_CLASS_OUTER_RULE_NAME,
+                IMPLICIT_CLASS_OUTER_RULE_NAME,
                 testRuleType.desc
             )
             visitFieldInsn(Opcodes.PUTSTATIC,
@@ -313,7 +312,7 @@
 
             visitFieldInsn(Opcodes.GETSTATIC,
                 ravenwoodTestRunnerType.internlName,
-                RavenwoodAwareTestRunner.IMPLICIT_CLASS_INNER_RULE_NAME,
+                IMPLICIT_CLASS_INNER_RULE_NAME,
                 testRuleType.desc
             )
             visitFieldInsn(Opcodes.PUTSTATIC,
@@ -361,7 +360,7 @@
             visitVarInsn(ALOAD, 0)
             visitFieldInsn(Opcodes.GETSTATIC,
                 ravenwoodTestRunnerType.internlName,
-                RavenwoodAwareTestRunner.IMPLICIT_INST_OUTER_RULE_NAME,
+                IMPLICIT_INST_OUTER_RULE_NAME,
                 testRuleType.desc
             )
             visitFieldInsn(Opcodes.PUTFIELD,
@@ -373,7 +372,7 @@
             visitVarInsn(ALOAD, 0)
             visitFieldInsn(Opcodes.GETSTATIC,
                 ravenwoodTestRunnerType.internlName,
-                RavenwoodAwareTestRunner.IMPLICIT_INST_INNER_RULE_NAME,
+                IMPLICIT_INST_INNER_RULE_NAME,
                 testRuleType.desc
             )
             visitFieldInsn(Opcodes.PUTFIELD,
@@ -435,6 +434,11 @@
     }
 
     companion object {
+        const val IMPLICIT_CLASS_OUTER_RULE_NAME = "sImplicitClassOuterRule"
+        const val IMPLICIT_CLASS_INNER_RULE_NAME = "sImplicitClassInnerRule"
+        const val IMPLICIT_INST_OUTER_RULE_NAME = "sImplicitInstOuterRule"
+        const val IMPLICIT_INST_INNER_RULE_NAME = "sImplicitInstInnerRule"
+
         fun shouldProcess(classes: ClassNodes, className: String): Boolean {
             if (!isTestLookingClass(classes, className)) {
                 return false
@@ -463,4 +467,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 5567707..0c99fcf 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -209,6 +209,10 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
 
             if (DEBUG) {
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 8b619a4..9b987e9 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -670,6 +670,17 @@
     }
 
     private void readAllPermissions() {
+        readAllPermissionsFromXml();
+        readAllPermissionsFromEnvironment();
+
+        // Apply global feature removal last, after all features have been read.
+        // This only needs to happen once.
+        for (String featureName : mUnavailableFeatures) {
+            removeFeature(featureName);
+        }
+    }
+
+    private void readAllPermissionsFromXml() {
         final XmlPullParser parser = Xml.newPullParser();
 
         // Read configuration from system
@@ -1732,7 +1743,13 @@
         } finally {
             IoUtils.closeQuietly(permReader);
         }
+    }
 
+    // Add features or permission dependent on global system properties (as
+    // opposed to XML permission files).
+    // This only needs to be called once after all features have been parsed
+    // from various partition/apex sources.
+    private void readAllPermissionsFromEnvironment() {
         // Some devices can be field-converted to FBE, so offer to splice in
         // those features if not already defined by the static config
         if (StorageManager.isFileEncrypted()) {
@@ -1773,10 +1790,6 @@
                 addFeature(PackageManager.FEATURE_EROFS_LEGACY, 0);
             }
         }
-
-        for (String featureName : mUnavailableFeatures) {
-            removeFeature(featureName);
-        }
     }
 
     private @Nullable SignedPackage parseEnhancedConfirmationTrustedPackage(XmlPullParser parser,
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 51c768b..06e6c8b 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -48,6 +48,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.vcn.Flags;
 import android.net.vcn.IVcnManagementService;
 import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
@@ -524,6 +525,9 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
 
             switch (action) {
                 case Intent.ACTION_PACKAGE_ADDED: // Fallthrough
@@ -878,6 +882,7 @@
 
     private void garbageCollectAndWriteVcnConfigsLocked() {
         final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
+        final Set<ParcelUuid> subGroups = mLastSnapshot.getAllSubscriptionGroups();
 
         boolean shouldWrite = false;
 
@@ -885,11 +890,20 @@
         while (configsIterator.hasNext()) {
             final ParcelUuid subGrp = configsIterator.next();
 
-            final List<SubscriptionInfo> subscriptions = subMgr.getSubscriptionsInGroup(subGrp);
-            if (subscriptions == null || subscriptions.isEmpty()) {
-                // Trim subGrps with no more subscriptions; must have moved to another subGrp
-                configsIterator.remove();
-                shouldWrite = true;
+            if (Flags.fixConfigGarbageCollection()) {
+                if (!subGroups.contains(subGrp)) {
+                    // Trim subGrps with no more subscriptions; must have moved to another subGrp
+                    logDbg("Garbage collect VcnConfig for group=" + subGrp);
+                    configsIterator.remove();
+                    shouldWrite = true;
+                }
+            } else {
+                final List<SubscriptionInfo> subscriptions = subMgr.getSubscriptionsInGroup(subGrp);
+                if (subscriptions == null || subscriptions.isEmpty()) {
+                    // Trim subGrps with no more subscriptions; must have moved to another subGrp
+                    configsIterator.remove();
+                    shouldWrite = true;
+                }
             }
         }
 
@@ -1094,13 +1108,7 @@
             synchronized (mLock) {
                 final Vcn vcn = mVcns.get(subGrp);
                 final VcnConfig vcnConfig = mConfigs.get(subGrp);
-                if (vcn != null) {
-                    if (vcnConfig == null) {
-                        // TODO: b/284381334 Investigate for the root cause of this issue
-                        // and handle it properly
-                        logWtf("Vcn instance exists but VcnConfig does not for " + subGrp);
-                    }
-
+                if (vcn != null && vcnConfig != null) {
                     if (vcn.getStatus() == VCN_STATUS_CODE_ACTIVE) {
                         isVcnManagedNetwork = true;
                     }
@@ -1120,6 +1128,8 @@
                             }
                         }
                     }
+                } else if (vcn != null && vcnConfig == null) {
+                    logWtf("Vcn instance exists but VcnConfig does not for " + subGrp);
                 }
             }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f549c7b..d11abf8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2874,8 +2874,12 @@
             // Add common services.
             // IMPORTANT: Before adding services here, make sure ephemeral apps can access them too.
             // Enable the check in ApplicationThread.bindApplication() to make sure.
-            if (!android.server.Flags.removeJavaServiceManagerCache()) {
-                addServiceToMap(mAppBindArgs, "permissionmgr");
+
+            // Removing User Service and App Ops Service from cache breaks boot for auto.
+            // Removing permissionmgr breaks tests for Android Auto due to SELinux restrictions.
+            // TODO: fix SELinux restrictions and remove caching for Android Auto.
+            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                    || !android.server.Flags.removeJavaServiceManagerCache()) {
                 addServiceToMap(mAppBindArgs, Context.ALARM_SERVICE);
                 addServiceToMap(mAppBindArgs, Context.DISPLAY_SERVICE);
                 addServiceToMap(mAppBindArgs, Context.NETWORKMANAGEMENT_SERVICE);
@@ -2896,12 +2900,12 @@
             // See b/79378449
             // Getting the window service and package service binder from servicemanager
             // is blocked for Apps. However they are necessary for apps.
-            // Removing User Service and App Ops Service from cache breaks boot for auto.
             // TODO: remove exception
-            addServiceToMap(mAppBindArgs, Context.APP_OPS_SERVICE);
             addServiceToMap(mAppBindArgs, "package");
             addServiceToMap(mAppBindArgs, Context.WINDOW_SERVICE);
             addServiceToMap(mAppBindArgs, Context.USER_SERVICE);
+            addServiceToMap(mAppBindArgs, "permissionmgr");
+            addServiceToMap(mAppBindArgs, Context.APP_OPS_SERVICE);
         }
         return mAppBindArgs;
     }
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 4c87e1c..c036605 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -373,7 +373,10 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
-            switch (intent.getAction()) {
+            if (action == null) {
+                return;
+            }
+            switch (action) {
                 case Intent.ACTION_PACKAGE_ADDED: {
                     if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                         final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index bbe7b2b..8436c80 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1975,7 +1975,6 @@
     void setAudioStatus(boolean mute, int volume) {
         if (!isTvDeviceEnabled()
                 || !tv().isSystemAudioActivated()
-                || !tv().isArcEstablished() // Don't update TV volume when SAM is on and ARC is off
                 || getHdmiCecVolumeControl()
                 == HdmiControlManager.VOLUME_CONTROL_DISABLED) {
             return;
diff --git a/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java b/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java
deleted file mode 100644
index f09e035e..0000000
--- a/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-
-import android.content.integrity.IntegrityUtils;
-
-import com.android.server.integrity.model.BitInputStream;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * Helper methods for reading standard data structures from {@link BitInputStream}.
- */
-public class BinaryFileOperations {
-
-    /**
-     * Read an string value with the given size and hash status from a {@code BitInputStream}.
-     *
-     * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form.
-     * All hashed values are hex-encoded.
-     */
-    public static String getStringValue(BitInputStream bitInputStream) throws IOException {
-        boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1;
-        int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS);
-        return getStringValue(bitInputStream, valueSize, isHashedValue);
-    }
-
-    /**
-     * Read an string value with the given size and hash status from a {@code BitInputStream}.
-     *
-     * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form.
-     * All hashed values are hex-encoded.
-     */
-    public static String getStringValue(
-            BitInputStream bitInputStream, int valueSize, boolean isHashedValue)
-            throws IOException {
-        if (!isHashedValue) {
-            StringBuilder value = new StringBuilder();
-            while (valueSize-- > 0) {
-                value.append((char) bitInputStream.getNext(/* numOfBits= */ 8));
-            }
-            return value.toString();
-        }
-        ByteBuffer byteBuffer = ByteBuffer.allocate(valueSize);
-        while (valueSize-- > 0) {
-            byteBuffer.put((byte) (bitInputStream.getNext(/* numOfBits= */ 8) & 0xFF));
-        }
-        return IntegrityUtils.getHexDigest(byteBuffer.array());
-    }
-
-    /** Read an integer value from a {@code BitInputStream}. */
-    public static int getIntValue(BitInputStream bitInputStream) throws IOException {
-        return bitInputStream.getNext(/* numOfBits= */ 32);
-    }
-
-    /** Read an boolean value from a {@code BitInputStream}. */
-    public static boolean getBooleanValue(BitInputStream bitInputStream) throws IOException {
-        return bitInputStream.getNext(/* numOfBits= */ 1) == 1;
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/parser/LimitInputStream.java b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java
deleted file mode 100644
index a91bbb7..0000000
--- a/services/core/java/com/android/server/integrity/parser/LimitInputStream.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/** An {@link InputStream} that basically truncates another {@link InputStream} */
-public class LimitInputStream extends FilterInputStream {
-    private int mReadBytes;
-    private final int mLimit;
-
-    public LimitInputStream(InputStream in, int limit) {
-        super(in);
-        if (limit < 0) {
-            throw new IllegalArgumentException("limit " + limit + " cannot be negative");
-        }
-        mReadBytes = 0;
-        mLimit = limit;
-    }
-
-    @Override
-    public int available() throws IOException {
-        return Math.min(super.available(), mLimit - mReadBytes);
-    }
-
-    @Override
-    public int read() throws IOException {
-        if (mReadBytes == mLimit) {
-            return -1;
-        }
-        mReadBytes++;
-        return super.read();
-    }
-
-    @Override
-    public int read(byte[] b) throws IOException {
-        return read(b, 0, b.length);
-    }
-
-    @Override
-    public int read(byte[] b, int off, int len) throws IOException {
-        if (len <= 0) {
-            return 0;
-        }
-        int available = available();
-        if (available <= 0) {
-            return -1;
-        }
-        int result = super.read(b, off, Math.min(len, available));
-        mReadBytes += result;
-        return result;
-    }
-
-    @Override
-    public long skip(long n) throws IOException {
-        if (n <= 0) {
-            return 0;
-        }
-        int available = available();
-        if (available <= 0) {
-            return 0;
-        }
-        int bytesToSkip = (int) Math.min(available, n);
-        long bytesSkipped = super.skip(bytesToSkip);
-        mReadBytes += (int) bytesSkipped;
-        return bytesSkipped;
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java
deleted file mode 100644
index 206e6a1..0000000
--- a/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/** A wrapper around {@link RandomAccessObject} to turn it into a {@link InputStream}. */
-public class RandomAccessInputStream extends InputStream {
-
-    private final RandomAccessObject mRandomAccessObject;
-
-    private int mPosition;
-
-    public RandomAccessInputStream(RandomAccessObject object) throws IOException {
-        mRandomAccessObject = object;
-        mPosition = 0;
-    }
-
-    /** Returns the position of the file pointer. */
-    public int getPosition() {
-        return mPosition;
-    }
-
-    /** See {@link RandomAccessObject#seek(int)} */
-    public void seek(int position) throws IOException {
-        mRandomAccessObject.seek(position);
-        mPosition = position;
-    }
-
-    @Override
-    public int available() throws IOException {
-        return mRandomAccessObject.length() - mPosition;
-    }
-
-    @Override
-    public void close() throws IOException {
-        mRandomAccessObject.close();
-    }
-
-    @Override
-    public int read() throws IOException {
-        if (available() <= 0) {
-            return -1;
-        }
-        mPosition++;
-        return mRandomAccessObject.read();
-    }
-
-    @Override
-    public int read(byte[] b) throws IOException {
-        return read(b, 0, b.length);
-    }
-
-    @Override
-    public int read(byte[] b, int off, int len) throws IOException {
-        if (len <= 0) {
-            return 0;
-        }
-        int available = available();
-        if (available <= 0) {
-            return -1;
-        }
-        int result = mRandomAccessObject.read(b, off, Math.min(len, available));
-        mPosition += result;
-        return result;
-    }
-
-    @Override
-    public long skip(long n) throws IOException {
-        if (n <= 0) {
-            return 0;
-        }
-        int available = available();
-        if (available <= 0) {
-            return 0;
-        }
-        int skipAmount = (int) Math.min(available, n);
-        mPosition += skipAmount;
-        mRandomAccessObject.seek(mPosition);
-        return skipAmount;
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java
deleted file mode 100644
index d9b2e38..0000000
--- a/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-
-/** An interface for random access objects like RandomAccessFile or byte arrays. */
-public abstract class RandomAccessObject {
-
-    /** See {@link RandomAccessFile#seek(long)}. */
-    public abstract void seek(int position) throws IOException;
-
-    /** See {@link RandomAccessFile#read()}. */
-    public abstract int read() throws IOException;
-
-    /** See {@link RandomAccessFile#read(byte[], int, int)}. */
-    public abstract int read(byte[] bytes, int off, int len) throws IOException;
-
-    /** See {@link RandomAccessFile#close()}. */
-    public abstract void close() throws IOException;
-
-    /** See {@link java.io.RandomAccessFile#length()}. */
-    public abstract int length();
-
-    /** Static constructor from a file. */
-    public static RandomAccessObject ofFile(File file) throws IOException {
-        return new RandomAccessFileObject(file);
-    }
-
-    /** Static constructor from a byte array. */
-    public static RandomAccessObject ofBytes(byte[] bytes) {
-        return new RandomAccessByteArrayObject(bytes);
-    }
-
-    private static class RandomAccessFileObject extends RandomAccessObject {
-        private final RandomAccessFile mRandomAccessFile;
-        // We cache the length since File.length() invokes file IO.
-        private final int mLength;
-
-        RandomAccessFileObject(File file) throws IOException {
-            long length = file.length();
-            if (length > Integer.MAX_VALUE) {
-                throw new IOException("Unsupported file size (too big) " + length);
-            }
-
-            mRandomAccessFile = new RandomAccessFile(file, /* mode= */ "r");
-            mLength = (int) length;
-        }
-
-        @Override
-        public void seek(int position) throws IOException {
-            mRandomAccessFile.seek(position);
-        }
-
-        @Override
-        public int read() throws IOException {
-            return mRandomAccessFile.read();
-        }
-
-        @Override
-        public int read(byte[] bytes, int off, int len) throws IOException {
-            return mRandomAccessFile.read(bytes, off, len);
-        }
-
-        @Override
-        public void close() throws IOException {
-            mRandomAccessFile.close();
-        }
-
-        @Override
-        public int length() {
-            return mLength;
-        }
-    }
-
-    private static class RandomAccessByteArrayObject extends RandomAccessObject {
-
-        private final ByteBuffer mBytes;
-
-        RandomAccessByteArrayObject(byte[] bytes) {
-            mBytes = ByteBuffer.wrap(bytes);
-        }
-
-        @Override
-        public void seek(int position) throws IOException {
-            mBytes.position(position);
-        }
-
-        @Override
-        public int read() throws IOException {
-            if (!mBytes.hasRemaining()) {
-                return -1;
-            }
-
-            return mBytes.get() & 0xFF;
-        }
-
-        @Override
-        public int read(byte[] bytes, int off, int len) throws IOException {
-            int bytesToCopy = Math.min(len, mBytes.remaining());
-            if (bytesToCopy <= 0) {
-                return 0;
-            }
-            mBytes.get(bytes, off, len);
-            return bytesToCopy;
-        }
-
-        @Override
-        public void close() throws IOException {}
-
-        @Override
-        public int length() {
-            return mBytes.capacity();
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
deleted file mode 100644
index ea3a3d5..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START;
-import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SIGNAL_BIT;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue;
-import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
-import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.InstallerAllowedByManifestFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import com.android.server.integrity.model.BitInputStream;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/** A helper class to parse rules into the {@link Rule} model from Binary representation. */
-public class RuleBinaryParser implements RuleParser {
-
-    @Override
-    public List<Rule> parse(byte[] ruleBytes) throws RuleParseException {
-        return parse(RandomAccessObject.ofBytes(ruleBytes), Collections.emptyList());
-    }
-
-    @Override
-    public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges)
-            throws RuleParseException {
-        try (RandomAccessInputStream randomAccessInputStream =
-                new RandomAccessInputStream(randomAccessObject)) {
-            return parseRules(randomAccessInputStream, indexRanges);
-        } catch (Exception e) {
-            throw new RuleParseException(e.getMessage(), e);
-        }
-    }
-
-    private List<Rule> parseRules(
-            RandomAccessInputStream randomAccessInputStream, List<RuleIndexRange> indexRanges)
-            throws IOException {
-
-        // Read the rule binary file format version.
-        randomAccessInputStream.skip(FORMAT_VERSION_BITS / BYTE_BITS);
-
-        return indexRanges.isEmpty()
-                ? parseAllRules(randomAccessInputStream)
-                : parseIndexedRules(randomAccessInputStream, indexRanges);
-    }
-
-    private List<Rule> parseAllRules(RandomAccessInputStream randomAccessInputStream)
-            throws IOException {
-        List<Rule> parsedRules = new ArrayList<>();
-
-        BitInputStream inputStream =
-                new BitInputStream(new BufferedInputStream(randomAccessInputStream));
-        while (inputStream.hasNext()) {
-            if (inputStream.getNext(SIGNAL_BIT) == 1) {
-                parsedRules.add(parseRule(inputStream));
-            }
-        }
-
-        return parsedRules;
-    }
-
-    private List<Rule> parseIndexedRules(
-            RandomAccessInputStream randomAccessInputStream, List<RuleIndexRange> indexRanges)
-            throws IOException {
-        List<Rule> parsedRules = new ArrayList<>();
-
-        for (RuleIndexRange range : indexRanges) {
-            randomAccessInputStream.seek(range.getStartIndex());
-
-            BitInputStream inputStream =
-                    new BitInputStream(
-                            new BufferedInputStream(
-                                    new LimitInputStream(
-                                            randomAccessInputStream,
-                                            range.getEndIndex() - range.getStartIndex())));
-
-            // Read the rules until we reach the end index. available() here is not reliable.
-            while (inputStream.hasNext()) {
-                if (inputStream.getNext(SIGNAL_BIT) == 1) {
-                    parsedRules.add(parseRule(inputStream));
-                }
-            }
-        }
-
-        return parsedRules;
-    }
-
-    private Rule parseRule(BitInputStream bitInputStream) throws IOException {
-        IntegrityFormula formula = parseFormula(bitInputStream);
-        int effect = bitInputStream.getNext(EFFECT_BITS);
-
-        if (bitInputStream.getNext(SIGNAL_BIT) != 1) {
-            throw new IllegalArgumentException("A rule must end with a '1' bit.");
-        }
-
-        return new Rule(formula, effect);
-    }
-
-    private IntegrityFormula parseFormula(BitInputStream bitInputStream) throws IOException {
-        int separator = bitInputStream.getNext(SEPARATOR_BITS);
-        switch (separator) {
-            case ATOMIC_FORMULA_START:
-                return parseAtomicFormula(bitInputStream);
-            case COMPOUND_FORMULA_START:
-                return parseCompoundFormula(bitInputStream);
-            case COMPOUND_FORMULA_END:
-                return null;
-            case INSTALLER_ALLOWED_BY_MANIFEST_START:
-                return new InstallerAllowedByManifestFormula();
-            default:
-                throw new IllegalArgumentException(
-                        String.format("Unknown formula separator: %s", separator));
-        }
-    }
-
-    private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) throws IOException {
-        int connector = bitInputStream.getNext(CONNECTOR_BITS);
-        List<IntegrityFormula> formulas = new ArrayList<>();
-
-        IntegrityFormula parsedFormula = parseFormula(bitInputStream);
-        while (parsedFormula != null) {
-            formulas.add(parsedFormula);
-            parsedFormula = parseFormula(bitInputStream);
-        }
-
-        return new CompoundFormula(connector, formulas);
-    }
-
-    private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) throws IOException {
-        int key = bitInputStream.getNext(KEY_BITS);
-        int operator = bitInputStream.getNext(OPERATOR_BITS);
-
-        switch (key) {
-            case AtomicFormula.PACKAGE_NAME:
-            case AtomicFormula.APP_CERTIFICATE:
-            case AtomicFormula.APP_CERTIFICATE_LINEAGE:
-            case AtomicFormula.INSTALLER_NAME:
-            case AtomicFormula.INSTALLER_CERTIFICATE:
-            case AtomicFormula.STAMP_CERTIFICATE_HASH:
-                boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1;
-                int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS);
-                String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue);
-                return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue);
-            case AtomicFormula.VERSION_CODE:
-                // TODO(b/147880712): temporary hack until our input handles long
-                long upper = getIntValue(bitInputStream);
-                long lower = getIntValue(bitInputStream);
-                long longValue = (upper << 32) | lower;
-                return new AtomicFormula.LongAtomicFormula(key, operator, longValue);
-            case AtomicFormula.PRE_INSTALLED:
-            case AtomicFormula.STAMP_TRUSTED:
-                boolean booleanValue = getBooleanValue(bitInputStream);
-                return new AtomicFormula.BooleanAtomicFormula(key, booleanValue);
-            default:
-                throw new IllegalArgumentException(String.format("Unknown key: %d", key));
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
deleted file mode 100644
index 595a035..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import android.annotation.Nullable;
-
-/**
- * A wrapper class to represent an indexing range that is identified by the {@link
- * RuleIndexingController}.
- */
-public class RuleIndexRange {
-    private int mStartIndex;
-    private int mEndIndex;
-
-    /** Constructor with start and end indexes. */
-    public RuleIndexRange(int startIndex, int endIndex) {
-        this.mStartIndex = startIndex;
-        this.mEndIndex = endIndex;
-    }
-
-    /** Returns the startIndex. */
-    public int getStartIndex() {
-        return mStartIndex;
-    }
-
-    /** Returns the end index. */
-    public int getEndIndex() {
-        return mEndIndex;
-    }
-
-    @Override
-    public boolean equals(@Nullable Object object) {
-        return mStartIndex == ((RuleIndexRange) object).getStartIndex()
-                && mEndIndex == ((RuleIndexRange) object).getEndIndex();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("Range{%d, %d}", mStartIndex, mEndIndex);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
deleted file mode 100644
index 348a03b..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
-import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
-
-import android.content.integrity.AppInstallMetadata;
-
-import com.android.server.integrity.model.BitInputStream;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/** Helper class to identify the necessary indexes that needs to be read. */
-public class RuleIndexingController {
-
-    private static LinkedHashMap<String, Integer> sPackageNameBasedIndexes;
-    private static LinkedHashMap<String, Integer> sAppCertificateBasedIndexes;
-    private static LinkedHashMap<String, Integer> sUnindexedRuleIndexes;
-
-    /**
-     * Provide the indexing file to read and the object will be constructed by reading and
-     * identifying the indexes.
-     */
-    public RuleIndexingController(InputStream inputStream) throws IOException {
-        BitInputStream bitInputStream = new BitInputStream(inputStream);
-        sPackageNameBasedIndexes = getNextIndexGroup(bitInputStream);
-        sAppCertificateBasedIndexes = getNextIndexGroup(bitInputStream);
-        sUnindexedRuleIndexes = getNextIndexGroup(bitInputStream);
-    }
-
-    /**
-     * Returns a list of integers with the starting and ending bytes of the rules that needs to be
-     * read and evaluated.
-     */
-    public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
-        List<RuleIndexRange> indexRanges = new ArrayList<>();
-
-        // Add the range for package name indexes rules.
-        indexRanges.add(
-                searchIndexingKeysRangeContainingKey(
-                        sPackageNameBasedIndexes, appInstallMetadata.getPackageName()));
-
-        // Add the range for app certificate indexes rules of all certificates.
-        for (String appCertificate : appInstallMetadata.getAppCertificates()) {
-            indexRanges.add(
-                    searchIndexingKeysRangeContainingKey(
-                            sAppCertificateBasedIndexes, appCertificate));
-        }
-
-        // Add the range for unindexed rules.
-        indexRanges.add(
-                new RuleIndexRange(
-                        sUnindexedRuleIndexes.get(START_INDEXING_KEY),
-                        sUnindexedRuleIndexes.get(END_INDEXING_KEY)));
-
-        return indexRanges;
-    }
-
-    private LinkedHashMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream)
-            throws IOException {
-        LinkedHashMap<String, Integer> keyToIndexMap = new LinkedHashMap<>();
-        while (bitInputStream.hasNext()) {
-            String key = getStringValue(bitInputStream);
-            int value = getIntValue(bitInputStream);
-
-            keyToIndexMap.put(key, value);
-
-            if (key.matches(END_INDEXING_KEY)) {
-                break;
-            }
-        }
-        if (keyToIndexMap.size() < 2) {
-            throw new IllegalStateException("Indexing file is corrupt.");
-        }
-        return keyToIndexMap;
-    }
-
-    private static RuleIndexRange searchIndexingKeysRangeContainingKey(
-            LinkedHashMap<String, Integer> indexMap, String searchedKey) {
-        List<String> keys = indexMap.keySet().stream().collect(Collectors.toList());
-        List<String> identifiedKeyRange =
-                searchKeysRangeContainingKey(keys, searchedKey, 0, keys.size() - 1);
-        return new RuleIndexRange(
-                indexMap.get(identifiedKeyRange.get(0)), indexMap.get(identifiedKeyRange.get(1)));
-    }
-
-    private static List<String> searchKeysRangeContainingKey(
-            List<String> sortedKeyList, String key, int startIndex, int endIndex) {
-        if (endIndex <= startIndex) {
-            throw new IllegalStateException("Indexing file is corrupt.");
-        }
-        if (endIndex - startIndex == 1) {
-            return Arrays.asList(sortedKeyList.get(startIndex), sortedKeyList.get(endIndex));
-        }
-
-        int midKeyIndex = startIndex + ((endIndex - startIndex) / 2);
-        String midKey = sortedKeyList.get(midKeyIndex);
-
-        if (key.compareTo(midKey) >= 0) {
-            return searchKeysRangeContainingKey(sortedKeyList, key, midKeyIndex, endIndex);
-        } else {
-            return searchKeysRangeContainingKey(sortedKeyList, key, startIndex, midKeyIndex);
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleParseException.java b/services/core/java/com/android/server/integrity/parser/RuleParseException.java
deleted file mode 100644
index c0f36a6..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleParseException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import android.annotation.NonNull;
-
-/**
- * Thrown when rule parsing fails.
- */
-public class RuleParseException extends Exception {
-    public RuleParseException(@NonNull String message) {
-        super(message);
-    }
-
-    public RuleParseException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java
deleted file mode 100644
index 126dacc..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleParser.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import android.content.integrity.Rule;
-
-import java.util.List;
-
-/** A helper class to parse rules into the {@link Rule} model. */
-public interface RuleParser {
-
-    /** Parse rules from bytes. */
-    List<Rule> parse(byte[] ruleBytes) throws RuleParseException;
-
-    /** Parse rules from an input stream. */
-    List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> ruleIndexRanges)
-            throws RuleParseException;
-}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index bbdac56..8798a64 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1183,9 +1183,7 @@
 
             // If config_disableWeaverOnUnsecuredUsers=true, then the Weaver HAL may be buggy and
             // need multiple retries before it works here to unwrap the SP, if the SP was already
-            // protected by Weaver.  Note that the problematic HAL can also deadlock if called with
-            // the ActivityManagerService lock held, but that should not be a problem here since
-            // that lock isn't held here, unlike unlockUserKeyIfUnsecured() where it is.
+            // protected by Weaver.
             for (int i = 0; i < 12 && sp == null; i++) {
                 Slog.e(TAG, "Failed to unwrap synthetic password. Waiting 5 seconds to retry.");
                 SystemClock.sleep(5000);
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 980f40e..9b02ed0 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -1597,6 +1597,9 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
 
             if (action.equals(Intent.ACTION_SCREEN_ON)) {
                 // Keep track of screen on/off state, but do not turn off the notification light
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 69c78eb..f9c1037 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4283,6 +4283,10 @@
                 if (intent == null) {
                     return;
                 }
+                final String action = intent.getAction();
+                if (action == null) {
+                    return;
+                }
                 Uri data = intent.getData();
                 if (data == null) {
                     return;
diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java
index 881bdbd..15fd35e 100644
--- a/services/core/java/com/android/server/slice/SliceManagerService.java
+++ b/services/core/java/com/android/server/slice/SliceManagerService.java
@@ -604,6 +604,11 @@
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action == null) {
+                Slog.w(TAG, "Intent broadcast does not contain action: " + intent);
+                return;
+            }
             final int userId  = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
             if (userId == UserHandle.USER_NULL) {
                 Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent);
@@ -615,7 +620,7 @@
                 Slog.w(TAG, "Intent broadcast does not contain package name: " + intent);
                 return;
             }
-            switch (intent.getAction()) {
+            switch (action) {
                 case Intent.ACTION_PACKAGE_REMOVED:
                     final boolean replacing =
                             intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
diff --git a/services/core/java/com/android/server/vcn/OWNERS b/services/core/java/com/android/server/vcn/OWNERS
index 2441e77..937699a 100644
--- a/services/core/java/com/android/server/vcn/OWNERS
+++ b/services/core/java/com/android/server/vcn/OWNERS
@@ -1,7 +1,6 @@
 set noparent
 
-benedictwong@google.com
-ckesting@google.com
 evitayan@google.com
-junyin@google.com
 nharold@google.com
+benedictwong@google.com #{LAST_RESORT_SUGGESTION}
+yangji@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index baf84cf..3392d03 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -436,6 +436,17 @@
             return mPrivilegedPackages.keySet();
         }
 
+        /** Returns all subscription groups */
+        @NonNull
+        public Set<ParcelUuid> getAllSubscriptionGroups() {
+            final Set<ParcelUuid> subGroups = new ArraySet<>();
+            for (SubscriptionInfo subInfo : mSubIdToInfoMap.values()) {
+                subGroups.add(subInfo.getGroupUuid());
+            }
+
+            return subGroups;
+        }
+
         /** Checks if the provided package is carrier privileged for the specified sub group. */
         public boolean packageHasPermissionsForSubscriptionGroup(
                 @NonNull ParcelUuid subGrp, @NonNull String packageName) {
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 5d2c50c..4310231 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -31,3 +31,7 @@
 
 # Files related to tracing
 per-file *TransitionTracer.java = file:platform/development:/tools/winscope/OWNERS
+
+# Files related to activity security
+per-file ActivityStarter.java = file:/ACTIVITY_SECURITY_OWNERS
+per-file ActivityTaskManagerService.java = file:/ACTIVITY_SECURITY_OWNERS
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 105147f..4e86888 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.profcollect;
 
+import android.Manifest;
+import android.annotation.RequiresPermission;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
@@ -26,6 +28,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.camera2.CameraManager;
+import android.hardware.usb.UsbManager;
 import android.os.Handler;
 import android.os.IBinder.DeathRecipient;
 import android.os.Looper;
@@ -67,6 +70,8 @@
     private int mUsageSetting;
     private boolean mUploadEnabled;
 
+    private boolean mAdbActive;
+
     private IProfCollectd mIProfcollect;
     private static ProfcollectForwardingService sSelfService;
     private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper());
@@ -84,6 +89,15 @@
                 Log.d(LOG_TAG, "Received broadcast to pack and upload reports");
                 createAndUploadReport(sSelfService);
             }
+            if (UsbManager.ACTION_USB_STATE.equals(intent.getAction())) {
+                boolean isADB = intent.getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false);
+                if (isADB) {
+                    boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
+                    Log.d(LOG_TAG, "Received broadcast that ADB became " + connected
+                            + ", was " + mAdbActive);
+                    mAdbActive = connected;
+                }
+            }
         }
     };
 
@@ -108,6 +122,7 @@
 
         final IntentFilter filter = new IntentFilter();
         filter.addAction(INTENT_UPLOAD_PROFILES);
+        filter.addAction(UsbManager.ACTION_USB_STATE);
         context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
     }
 
@@ -125,7 +140,13 @@
     }
 
     @Override
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
     public void onBootPhase(int phase) {
+        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            UsbManager usbManager = getContext().getSystemService(UsbManager.class);
+            mAdbActive = ((usbManager.getCurrentFunctions() & UsbManager.FUNCTION_ADB) == 1);
+            Log.d(LOG_TAG, "ADB is " + mAdbActive + " on system startup");
+        }
         if (phase == PHASE_BOOT_COMPLETED) {
             if (mIProfcollect == null) {
                 return;
@@ -281,6 +302,9 @@
             if (mIProfcollect == null) {
                 return;
             }
+            if (mAdbActive) {
+                return;
+            }
             if (Utils.withFrequency("applaunch_trace_freq", 5)) {
                 Utils.traceSystem(mIProfcollect, "applaunch");
             }
@@ -303,6 +327,9 @@
         if (mIProfcollect == null) {
             return;
         }
+        if (mAdbActive) {
+            return;
+        }
         if (Utils.withFrequency("dex2oat_trace_freq", 25)) {
             // Dex2oat could take a while before it starts. Add a short delay before start tracing.
             Utils.traceSystem(mIProfcollect, "dex2oat", /* delayMs */ 1000);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
index 1d668cd..13cf125 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
@@ -17,6 +17,7 @@
 package com.android.server.pm.parsing
 
 import android.content.pm.PackageManager
+import android.content.pm.parsing.ApkLiteParseUtils
 import android.platform.test.annotations.Postsubmit
 import com.android.internal.pm.parsing.PackageParserException
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
@@ -81,8 +82,10 @@
         val exceptions = buildApks()
                 .map {
                     runCatching {
-                        parser.parsePackage(
+                        if (ApkLiteParseUtils.isApkFile(it) || it.isDirectory()) {
+                            parser.parsePackage(
                                 it, ParsingPackageUtils.PARSE_IS_SYSTEM_DIR, false /*useCaches*/)
+                        }
                     }
                 }
                 .mapNotNull { it.exceptionOrNull() }
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index cedf9db..631f76a 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -65,6 +65,7 @@
         "androidx.annotation_annotation",
         "androidx.test.rules",
         "androidx.test.uiautomator_uiautomator",
+        "androidx.test.ext.junit",
         "modules-utils-binary-xml",
         "flag-junit",
     ],
diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING
index 4e209f4..d3d3cf6 100644
--- a/services/tests/powerstatstests/TEST_MAPPING
+++ b/services/tests/powerstatstests/TEST_MAPPING
@@ -9,8 +9,7 @@
       "name": "PowerStatsTestsRavenwood",
       "host": true,
       "options": [
-        {"include-filter": "com.android.server.power.stats"},
-        {"exclude-annotation": "android.platform.test.annotations.DisabledOnRavenwood"}
+        {"include-filter": "com.android.server.power.stats"}
       ]
     }
   ],
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
deleted file mode 100644
index 723b6c5..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue;
-import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
-import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
-import static com.android.server.integrity.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.IntegrityUtils;
-
-import com.android.server.integrity.model.BitInputStream;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
-@RunWith(JUnit4.class)
-public class BinaryFileOperationsTest {
-
-    private static final String IS_NOT_HASHED = "0";
-    private static final String IS_HASHED = "1";
-    private static final String PACKAGE_NAME = "com.test.app";
-    private static final String APP_CERTIFICATE = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
-
-    @Test
-    public void testGetStringValue() throws IOException {
-        byte[] stringBytes =
-                getBytes(
-                        IS_NOT_HASHED
-                                + getBits(PACKAGE_NAME.length(), VALUE_SIZE_BITS)
-                                + getValueBits(PACKAGE_NAME));
-        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(stringBytes));
-
-        String resultString = getStringValue(inputStream);
-
-        assertThat(resultString).isEqualTo(PACKAGE_NAME);
-    }
-
-    @Test
-    public void testGetHashedStringValue() throws IOException {
-        byte[] ruleBytes =
-                getBytes(
-                        IS_HASHED
-                                + getBits(APP_CERTIFICATE.length(), VALUE_SIZE_BITS)
-                                + getValueBits(APP_CERTIFICATE));
-        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
-
-        String resultString = getStringValue(inputStream);
-
-        assertThat(resultString)
-                .isEqualTo(IntegrityUtils.getHexDigest(
-                        APP_CERTIFICATE.getBytes(StandardCharsets.UTF_8)));
-    }
-
-    @Test
-    public void testGetStringValue_withSizeAndHashingInfo() throws IOException {
-        byte[] ruleBytes = getBytes(getValueBits(PACKAGE_NAME));
-        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
-
-        String resultString = getStringValue(inputStream,
-                PACKAGE_NAME.length(), /* isHashedValue= */false);
-
-        assertThat(resultString).isEqualTo(PACKAGE_NAME);
-    }
-
-    @Test
-    public void testGetIntValue() throws IOException {
-        int randomValue = 15;
-        byte[] ruleBytes = getBytes(getBits(randomValue, /* numOfBits= */ 32));
-        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
-
-        assertThat(getIntValue(inputStream)).isEqualTo(randomValue);
-    }
-
-    @Test
-    public void testGetBooleanValue_true() throws IOException {
-        String booleanValue = "1";
-        byte[] ruleBytes = getBytes(booleanValue);
-        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
-
-        assertThat(getBooleanValue(inputStream)).isEqualTo(true);
-    }
-
-    @Test
-    public void testGetBooleanValue_false() throws IOException {
-        String booleanValue = "0";
-        byte[] ruleBytes = getBytes(booleanValue);
-        BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
-
-        assertThat(getBooleanValue(inputStream)).isEqualTo(false);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
deleted file mode 100644
index 03363a1..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
+++ /dev/null
@@ -1,693 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class RuleBinaryParserTest {
-
-    private static final String COMPOUND_FORMULA_START_BITS =
-            getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
-    private static final String COMPOUND_FORMULA_END_BITS =
-            getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS);
-    private static final String ATOMIC_FORMULA_START_BITS =
-            getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS);
-    private static final int INVALID_FORMULA_SEPARATOR_VALUE = (1 << SEPARATOR_BITS) - 1;
-    private static final String INVALID_FORMULA_SEPARATOR_BITS =
-            getBits(INVALID_FORMULA_SEPARATOR_VALUE, SEPARATOR_BITS);
-
-    private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS);
-    private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS);
-    private static final String OR = getBits(CompoundFormula.OR, CONNECTOR_BITS);
-    private static final int INVALID_CONNECTOR_VALUE = 3;
-    private static final String INVALID_CONNECTOR =
-            getBits(INVALID_CONNECTOR_VALUE, CONNECTOR_BITS);
-
-    private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
-    private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS);
-    private static final String APP_CERTIFICATE_LINEAGE =
-            getBits(AtomicFormula.APP_CERTIFICATE_LINEAGE, KEY_BITS);
-    private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS);
-    private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS);
-    private static final int INVALID_KEY_VALUE = 9;
-    private static final String INVALID_KEY = getBits(INVALID_KEY_VALUE, KEY_BITS);
-
-    private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
-    private static final int INVALID_OPERATOR_VALUE = 5;
-    private static final String INVALID_OPERATOR = getBits(INVALID_OPERATOR_VALUE, OPERATOR_BITS);
-
-    private static final String IS_NOT_HASHED = "0";
-    private static final String IS_HASHED = "1";
-
-    private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
-    private static final int INVALID_EFFECT_VALUE = 5;
-    private static final String INVALID_EFFECT = getBits(INVALID_EFFECT_VALUE, EFFECT_BITS);
-
-    private static final String START_BIT = "1";
-    private static final String END_BIT = "1";
-    private static final String INVALID_MARKER_BIT = "0";
-
-    private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
-            getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
-
-    private static final List<RuleIndexRange> NO_INDEXING = Collections.emptyList();
-
-    @Test
-    public void testBinaryStream_validCompoundFormula_noIndexing() throws Exception {
-        String packageName = "com.test.app";
-        String ruleBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-        Rule expectedRule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Collections.singletonList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-
-        List<Rule> rules =
-                binaryParser.parse(RandomAccessObject.ofBytes(rule.array()), NO_INDEXING);
-
-        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
-    }
-
-    @Test
-    public void testBinaryString_validCompoundFormula_notConnector_noIndexing() throws Exception {
-        String packageName = "com.test.app";
-        String ruleBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-        Rule expectedRule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Collections.singletonList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-
-        List<Rule> rules = binaryParser.parse(rule.array());
-
-        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
-    }
-
-    @Test
-    public void testBinaryString_validCompoundFormula_andConnector_noIndexing() throws Exception {
-        String packageName = "com.test.app";
-        String appCertificate = "test_cert";
-        String ruleBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + AND
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-        Rule expectedRule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.AND,
-                                Arrays.asList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false),
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.APP_CERTIFICATE,
-                                                appCertificate,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-        List<Rule> rules = binaryParser.parse(rule.array());
-
-        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
-    }
-
-    @Test
-    public void testBinaryString_validCompoundFormula_orConnector_noIndexing() throws Exception {
-        String packageName = "com.test.app";
-        String appCertificate = "test_cert";
-        String ruleBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + OR
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-        Rule expectedRule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.OR,
-                                Arrays.asList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false),
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.APP_CERTIFICATE,
-                                                appCertificate,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-
-        List<Rule> rules = binaryParser.parse(rule.array());
-
-        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
-    }
-
-    @Test
-    public void testBinaryString_validAtomicFormula_stringValue_noIndexing() throws Exception {
-        String packageName = "com.test.app";
-        String ruleBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-        Rule expectedRule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.PACKAGE_NAME,
-                                packageName,
-                                /* isHashedValue= */ false),
-                        Rule.DENY);
-
-        List<Rule> rules = binaryParser.parse(rule.array());
-
-        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
-    }
-
-    @Test
-    public void testBinaryString_validAtomicFormula_hashedValue_noIndexing() throws Exception {
-        String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
-        String ruleBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-
-        RuleParser binaryParser = new RuleBinaryParser();
-        Rule expectedRule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.APP_CERTIFICATE,
-                                IntegrityUtils.getHexDigest(
-                                        appCertificate.getBytes(StandardCharsets.UTF_8)),
-                                /* isHashedValue= */ true),
-                        Rule.DENY);
-
-        List<Rule> rules = binaryParser.parse(rule.array());
-
-        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
-    }
-
-    @Test
-    public void testBinaryString_validAtomicFormulaWithCertificateLineage() throws Exception {
-        String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
-        String ruleBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE_LINEAGE
-                        + EQ
-                        + IS_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-
-        RuleParser binaryParser = new RuleBinaryParser();
-        Rule expectedRule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.APP_CERTIFICATE_LINEAGE,
-                                IntegrityUtils.getHexDigest(
-                                        appCertificate.getBytes(StandardCharsets.UTF_8)),
-                                /* isHashedValue= */ true),
-                        Rule.DENY);
-
-        List<Rule> rules = binaryParser.parse(rule.array());
-
-        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
-    }
-
-    @Test
-    public void testBinaryString_validAtomicFormula_integerValue_noIndexing() throws Exception {
-        int versionCode = 1;
-        String ruleBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + VERSION_CODE
-                        + EQ
-                        + getBits(versionCode, /* numOfBits= */ 64)
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-        Rule expectedRule =
-                new Rule(
-                        new AtomicFormula.LongAtomicFormula(
-                                AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 1),
-                        Rule.DENY);
-
-        List<Rule> rules = binaryParser.parse(rule.array());
-
-        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
-    }
-
-    @Test
-    public void testBinaryString_validAtomicFormula_booleanValue_noIndexing() throws Exception {
-        String isPreInstalled = "1";
-        String ruleBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PRE_INSTALLED
-                        + EQ
-                        + isPreInstalled
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-        Rule expectedRule =
-                new Rule(
-                        new AtomicFormula.BooleanAtomicFormula(
-                                AtomicFormula.PRE_INSTALLED, true),
-                        Rule.DENY);
-
-        List<Rule> rules = binaryParser.parse(rule.array());
-
-        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
-    }
-
-    @Test
-    public void testBinaryString_invalidAtomicFormula_noIndexing() {
-        int versionCode = 1;
-        String ruleBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + VERSION_CODE
-                        + EQ
-                        + getBits(versionCode, /* numOfBits= */ 64)
-                        + DENY;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        assertExpectException(
-                RuleParseException.class,
-                /* expectedExceptionMessageRegex= */ "A rule must end with a '1' bit.",
-                () -> binaryParser.parse(rule.array()));
-    }
-
-    @Test
-    public void testBinaryString_withNoRuleList_noIndexing() throws RuleParseException {
-        ByteBuffer rule = ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        List<Rule> rules = binaryParser.parse(rule.array());
-
-        assertThat(rules).isEmpty();
-    }
-
-    @Test
-    public void testBinaryString_withEmptyRule_noIndexing() {
-        String ruleBits = START_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        assertExpectException(
-                RuleParseException.class,
-                /* expectedExceptionMessageRegex */ "",
-                () -> binaryParser.parse(rule.array()));
-    }
-
-    @Test
-    public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas_noIndexing() {
-        String packageName = "com.test.app";
-        String appCertificate = "test_cert";
-        String ruleBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        assertExpectException(
-                RuleParseException.class,
-                /* expectedExceptionMessageRegex */ "Connector NOT must have 1 formula only",
-                () -> binaryParser.parse(rule.array()));
-    }
-
-    @Test
-    public void testBinaryString_invalidRule_invalidOperator_noIndexing() {
-        int versionCode = 1;
-        String ruleBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + VERSION_CODE
-                        + INVALID_OPERATOR
-                        + getBits(versionCode, /* numOfBits= */ 64)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        assertExpectException(
-                RuleParseException.class,
-                /* expectedExceptionMessageRegex */ String.format(
-                        "Unknown operator: %d", INVALID_OPERATOR_VALUE),
-                () -> binaryParser.parse(rule.array()));
-    }
-
-    @Test
-    public void testBinaryString_invalidRule_invalidEffect_noIndexing() {
-        String packageName = "com.test.app";
-        String ruleBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + INVALID_EFFECT
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        assertExpectException(
-                RuleParseException.class,
-                /* expectedExceptionMessageRegex */ String.format(
-                        "Unknown effect: %d", INVALID_EFFECT_VALUE),
-                () -> binaryParser.parse(rule.array()));
-    }
-
-    @Test
-    public void testBinaryString_invalidRule_invalidConnector_noIndexing() {
-        String packageName = "com.test.app";
-        String ruleBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + INVALID_CONNECTOR
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        assertExpectException(
-                RuleParseException.class,
-                /* expectedExceptionMessageRegex */ String.format(
-                        "Unknown connector: %d", INVALID_CONNECTOR_VALUE),
-                () -> binaryParser.parse(rule.array()));
-    }
-
-    @Test
-    public void testBinaryString_invalidRule_invalidKey_noIndexing() {
-        String packageName = "com.test.app";
-        String ruleBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + INVALID_KEY
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        assertExpectException(
-                RuleParseException.class,
-                /* expectedExceptionMessageRegex */ String.format(
-                        "Unknown key: %d", INVALID_KEY_VALUE),
-                () -> binaryParser.parse(rule.array()));
-    }
-
-    @Test
-    public void testBinaryString_invalidRule_invalidSeparator_noIndexing() {
-        String packageName = "com.test.app";
-        String ruleBits =
-                START_BIT
-                        + INVALID_FORMULA_SEPARATOR_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        assertExpectException(
-                RuleParseException.class,
-                /* expectedExceptionMessageRegex */ String.format(
-                        "Unknown formula separator: %d", INVALID_FORMULA_SEPARATOR_VALUE),
-                () -> binaryParser.parse(rule.array()));
-    }
-
-    @Test
-    public void testBinaryString_invalidRule_invalidEndMarker_noIndexing() {
-        String packageName = "com.test.app";
-        String ruleBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + INVALID_MARKER_BIT;
-        byte[] ruleBytes = getBytes(ruleBits);
-        ByteBuffer rule =
-                ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes);
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        assertExpectException(
-                RuleParseException.class,
-                /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit",
-                () -> binaryParser.parse(rule.array()));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
deleted file mode 100644
index 370bd80..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.integrity.AppInstallMetadata;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class RuleIndexingControllerTest {
-
-    @Test
-    public void verifyIndexRangeSearchIsCorrect() throws IOException {
-        InputStream inputStream = obtainDefaultIndexingMapForTest();
-
-        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
-        AppInstallMetadata appInstallMetadata =
-                new AppInstallMetadata.Builder()
-                        .setPackageName("ddd")
-                        .setAppCertificates(Collections.singletonList("777"))
-                        .setAppCertificateLineage(Collections.singletonList("777"))
-                        .build();
-
-        List<RuleIndexRange> resultingIndexes =
-                indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-        assertThat(resultingIndexes)
-                .containsExactly(
-                        new RuleIndexRange(200, 300),
-                        new RuleIndexRange(700, 800),
-                        new RuleIndexRange(900, 945));
-    }
-
-    @Test
-    public void verifyIndexRangeSearchIsCorrect_multipleAppCertificates() throws IOException {
-        InputStream inputStream = obtainDefaultIndexingMapForTest();
-
-        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
-        AppInstallMetadata appInstallMetadata =
-                new AppInstallMetadata.Builder()
-                        .setPackageName("ddd")
-                        .setAppCertificates(Arrays.asList("777", "999"))
-                        .setAppCertificateLineage(Arrays.asList("777", "999"))
-                        .build();
-
-        List<RuleIndexRange> resultingIndexes =
-                indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-        assertThat(resultingIndexes)
-                .containsExactly(
-                        new RuleIndexRange(200, 300),
-                        new RuleIndexRange(700, 800),
-                        new RuleIndexRange(800, 900),
-                        new RuleIndexRange(900, 945));
-    }
-
-    @Test
-    public void verifyIndexRangeSearchIsCorrect_keysInFirstAndLastBlock() throws IOException {
-        InputStream inputStream = obtainDefaultIndexingMapForTest();
-
-        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
-        AppInstallMetadata appInstallMetadata =
-                new AppInstallMetadata.Builder()
-                        .setPackageName("bbb")
-                        .setAppCertificates(Collections.singletonList("999"))
-                        .setAppCertificateLineage(Collections.singletonList("999"))
-                        .build();
-
-        List<RuleIndexRange> resultingIndexes =
-                indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-        assertThat(resultingIndexes)
-                .containsExactly(
-                        new RuleIndexRange(100, 200),
-                        new RuleIndexRange(800, 900),
-                        new RuleIndexRange(900, 945));
-    }
-
-    @Test
-    public void verifyIndexRangeSearchIsCorrect_keysMatchWithValues() throws IOException {
-        InputStream inputStream = obtainDefaultIndexingMapForTest();
-
-        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
-        AppInstallMetadata appInstallMetadata =
-                new AppInstallMetadata.Builder()
-                        .setPackageName("ccc")
-                        .setAppCertificates(Collections.singletonList("444"))
-                        .setAppCertificateLineage(Collections.singletonList("444"))
-                        .build();
-
-        List<RuleIndexRange> resultingIndexes =
-                indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-        assertThat(resultingIndexes)
-                .containsExactly(
-                        new RuleIndexRange(200, 300),
-                        new RuleIndexRange(700, 800),
-                        new RuleIndexRange(900, 945));
-    }
-
-    @Test
-    public void verifyIndexRangeSearchIsCorrect_noIndexesAvailable() throws IOException {
-        byte[] stringBytes =
-                getBytes(
-                        getKeyValueString(START_INDEXING_KEY, 100)
-                                + getKeyValueString(END_INDEXING_KEY, 500)
-                                + getKeyValueString(START_INDEXING_KEY, 500)
-                                + getKeyValueString(END_INDEXING_KEY, 900)
-                                + getKeyValueString(START_INDEXING_KEY, 900)
-                                + getKeyValueString(END_INDEXING_KEY, 945));
-        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
-        rule.put(stringBytes);
-        InputStream inputStream = new ByteArrayInputStream(rule.array());
-
-        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
-
-        AppInstallMetadata appInstallMetadata =
-                new AppInstallMetadata.Builder()
-                        .setPackageName("ccc")
-                        .setAppCertificates(Collections.singletonList("444"))
-                        .setAppCertificateLineage(Collections.singletonList("444"))
-                        .build();
-
-        List<RuleIndexRange> resultingIndexes =
-                indexingController.identifyRulesToEvaluate(appInstallMetadata);
-
-        assertThat(resultingIndexes)
-                .containsExactly(
-                        new RuleIndexRange(100, 500),
-                        new RuleIndexRange(500, 900),
-                        new RuleIndexRange(900, 945));
-    }
-
-    @Test
-    public void verifyIndexingFileIsCorrupt() throws IOException {
-        byte[] stringBytes =
-                getBytes(
-                        getKeyValueString(START_INDEXING_KEY, 100)
-                                + getKeyValueString("ccc", 200)
-                                + getKeyValueString(END_INDEXING_KEY, 300)
-                                + getKeyValueString(END_INDEXING_KEY, 900));
-        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
-        rule.put(stringBytes);
-        InputStream inputStream = new ByteArrayInputStream(rule.array());
-
-        assertThrows(IllegalStateException.class,
-                () -> new RuleIndexingController(inputStream));
-    }
-
-    private static InputStream obtainDefaultIndexingMapForTest() {
-        byte[] stringBytes =
-                getBytes(
-                        getKeyValueString(START_INDEXING_KEY, 100)
-                                + getKeyValueString("ccc", 200)
-                                + getKeyValueString("eee", 300)
-                                + getKeyValueString("hhh", 400)
-                                + getKeyValueString(END_INDEXING_KEY, 500)
-                                + getKeyValueString(START_INDEXING_KEY, 500)
-                                + getKeyValueString("111", 600)
-                                + getKeyValueString("444", 700)
-                                + getKeyValueString("888", 800)
-                                + getKeyValueString(END_INDEXING_KEY, 900)
-                                + getKeyValueString(START_INDEXING_KEY, 900)
-                                + getKeyValueString(END_INDEXING_KEY, 945));
-        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
-        rule.put(stringBytes);
-        return new ByteArrayInputStream(rule.array());
-    }
-
-    private static String getKeyValueString(String key, int value) {
-        String isNotHashed = "0";
-        return isNotHashed
-                + getBits(key.length(), VALUE_SIZE_BITS)
-                + getValueBits(key)
-                + getBits(value, /* numOfBits= */ 32);
-    }
-}
diff --git a/tests/vcn/OWNERS b/tests/vcn/OWNERS
index 2441e77..937699a 100644
--- a/tests/vcn/OWNERS
+++ b/tests/vcn/OWNERS
@@ -1,7 +1,6 @@
 set noparent
 
-benedictwong@google.com
-ckesting@google.com
 evitayan@google.com
-junyin@google.com
 nharold@google.com
+benedictwong@google.com #{LAST_RESORT_SUGGESTION}
+yangji@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 7e0bbc4..3828a71 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -70,6 +70,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.Uri;
+import android.net.vcn.Flags;
 import android.net.vcn.IVcnStatusCallback;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
@@ -84,6 +85,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.test.TestLooper;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -102,6 +104,7 @@
 import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -119,6 +122,8 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class VcnManagementServiceTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final String CONTEXT_ATTRIBUTION_TAG = "VCN";
     private static final String TEST_PACKAGE_NAME =
             VcnManagementServiceTest.class.getPackage().getName();
@@ -288,6 +293,8 @@
         doReturn(Collections.singleton(TRANSPORT_WIFI))
                 .when(mMockDeps)
                 .getRestrictedTransports(any(), any(), any());
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_FIX_CONFIG_GARBAGE_COLLECTION);
     }
 
 
@@ -438,6 +445,14 @@
             return subIds;
         }).when(snapshot).getAllSubIdsInGroup(any());
 
+        doAnswer(invocation -> {
+            final Set<ParcelUuid> subGroups = new ArraySet<>();
+            for (Entry<Integer, ParcelUuid> entry : subIdToGroupMap.entrySet()) {
+                subGroups.add(entry.getValue());
+            }
+            return subGroups;
+        }).when(snapshot).getAllSubscriptionGroups();
+
         return snapshot;
     }
 
@@ -1483,6 +1498,28 @@
     }
 
     @Test
+    public void testGarbageCollectionKeepConfigUntilNewSnapshot() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+        startAndGetVcnInstance(TEST_UUID_2);
+
+        // Report loss of subscription from mSubMgr
+        doReturn(Collections.emptyList()).when(mSubMgr).getSubscriptionsInGroup(any());
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                TEST_UUID_2,
+                Collections.singleton(TEST_UUID_2),
+                Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_2));
+
+        assertTrue(mVcnMgmtSvc.getConfigs().containsKey(TEST_UUID_2));
+
+        // Report loss of subscription from snapshot
+        triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet());
+
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        assertFalse(mVcnMgmtSvc.getConfigs().containsKey(TEST_UUID_2));
+    }
+
+    @Test
     public void testVcnCarrierConfigChangeUpdatesPolicyListener() throws Exception {
         setupActiveSubscription(TEST_UUID_2);