Merge "Create empty VCN libraries" into main
diff --git a/OWNERS b/OWNERS
index eb2bfcf..d0a634e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -47,3 +47,4 @@
 per-file BROADCASTS_OWNERS = file:/BROADCASTS_OWNERS
 per-file ADPF_OWNERS = file:/ADPF_OWNERS
 per-file GAME_MANAGER_OWNERS = file:/GAME_MANAGER_OWNERS
+per-file SDK_OWNERS = file:/SDK_OWNERS
diff --git a/SDK_OWNERS b/SDK_OWNERS
new file mode 100644
index 0000000..c9ca47a
--- /dev/null
+++ b/SDK_OWNERS
@@ -0,0 +1,6 @@
+amhk@google.com
+kimalexander@google.com
+lus@google.com
+michaelwr@google.com
+nanaasiedu@google.com
+paulduffin@google.com
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index a413bbd..16f0693 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -1,13 +1,16 @@
 android.content.AsyncTaskLoader$LoadTask
+android.media.MediaCodecInfo$CodecCapabilities$FeatureList
 android.net.ConnectivityThread$Singleton
 android.os.FileObserver
 android.os.NullVibrator
+android.permission.PermissionManager
+android.provider.MediaStore
 android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
+android.view.HdrRenderState
 android.widget.Magnifier
+com.android.internal.jank.InteractionJankMonitor$InstanceHolder
+com.android.internal.util.LatencyTracker$SLatencyTrackerHolder
 gov.nist.core.net.DefaultNetworkLayer
-android.net.rtp.AudioGroup
-android.net.rtp.AudioStream
-android.net.rtp.RtpStream
 java.util.concurrent.ThreadLocalRandom
 java.util.ImmutableCollections
-com.android.internal.jank.InteractionJankMonitor$InstanceHolder
+sun.nio.fs.UnixChannelFactory
diff --git a/core/api/current.txt b/core/api/current.txt
index 6b342a5..6d9fe44 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -22920,6 +22920,65 @@
     field public static final int AC4Profile11 = 514; // 0x202
     field public static final int AC4Profile21 = 1026; // 0x402
     field public static final int AC4Profile22 = 1028; // 0x404
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band0 = 513; // 0x201
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band1 = 514; // 0x202
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band2 = 516; // 0x204
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band3 = 520; // 0x208
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band0 = 257; // 0x101
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band1 = 258; // 0x102
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band2 = 260; // 0x104
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band3 = 264; // 0x108
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band0 = 2049; // 0x801
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band1 = 2050; // 0x802
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band2 = 2052; // 0x804
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band3 = 2056; // 0x808
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band0 = 1025; // 0x401
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band1 = 1026; // 0x402
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band2 = 1028; // 0x404
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band3 = 1032; // 0x408
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band0 = 8193; // 0x2001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band1 = 8194; // 0x2002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band2 = 8196; // 0x2004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band3 = 8200; // 0x2008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band0 = 4097; // 0x1001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band1 = 4098; // 0x1002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band2 = 4100; // 0x1004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band3 = 4104; // 0x1008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band0 = 32769; // 0x8001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band1 = 32770; // 0x8002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band2 = 32772; // 0x8004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band3 = 32776; // 0x8008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band0 = 16385; // 0x4001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band1 = 16386; // 0x4002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band2 = 16388; // 0x4004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band3 = 16392; // 0x4008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band0 = 131073; // 0x20001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band1 = 131074; // 0x20002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band2 = 131076; // 0x20004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band3 = 131080; // 0x20008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band0 = 65537; // 0x10001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band1 = 65538; // 0x10002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band2 = 65540; // 0x10004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band3 = 65544; // 0x10008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band0 = 524289; // 0x80001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band1 = 524290; // 0x80002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band2 = 524292; // 0x80004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band3 = 524296; // 0x80008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band0 = 262145; // 0x40001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band1 = 262146; // 0x40002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band2 = 262148; // 0x40004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band3 = 262152; // 0x40008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band0 = 2097153; // 0x200001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band1 = 2097154; // 0x200002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band2 = 2097156; // 0x200004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band3 = 2097160; // 0x200008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band0 = 1048577; // 0x100001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band1 = 1048578; // 0x100002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band2 = 1048580; // 0x100004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band3 = 1048584; // 0x100008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVProfile422_10 = 1; // 0x1
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVProfile422_10HDR10 = 4096; // 0x1000
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVProfile422_10HDR10Plus = 8192; // 0x2000
     field public static final int AV1Level2 = 1; // 0x1
     field public static final int AV1Level21 = 2; // 0x2
     field public static final int AV1Level22 = 4; // 0x4
@@ -23770,6 +23829,7 @@
     field public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
     field public static final String MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
     field public static final String MIMETYPE_TEXT_VTT = "text/vtt";
+    field @FlaggedApi("android.media.codec.apv_support") public static final String MIMETYPE_VIDEO_APV = "video/apv";
     field public static final String MIMETYPE_VIDEO_AV1 = "video/av01";
     field public static final String MIMETYPE_VIDEO_AVC = "video/avc";
     field public static final String MIMETYPE_VIDEO_DOLBY_VISION = "video/dolby-vision";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 9a294fd..0b891f6 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -102,6 +102,7 @@
     method @NonNull public android.os.UserHandle getUser();
     field public static final String PAC_PROXY_SERVICE = "pac_proxy";
     field public static final String TEST_NETWORK_SERVICE = "test_network";
+    field @FlaggedApi("android.os.mainline_vcn_platform_api") public static final String VCN_MANAGEMENT_SERVICE = "vcn_management";
     field @FlaggedApi("android.webkit.update_service_ipc_wrapper") public static final String WEBVIEW_UPDATE_SERVICE = "webviewupdate";
   }
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 36bdf73..744f019 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4855,6 +4855,8 @@
      * @see android.net.vcn.VcnManager
      * @hide
      */
+    @FlaggedApi(android.os.Flags.FLAG_MAINLINE_VCN_PLATFORM_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String VCN_MANAGEMENT_SERVICE = "vcn_management";
 
     /**
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index 41585b3..7d6e7ad 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -215,7 +215,7 @@
             // 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()
+                    android.database.sqlite.Flags.onewayFinalizerCloseFixed()
                     && "FinalizerDaemon".equals(Thread.currentThread().getName());
             mRemote.transact(CLOSE_TRANSACTION, data, reply,
                     fromFinalizer ? IBinder.FLAG_ONEWAY: 0);
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index c597895..c2cc9d1 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -2,8 +2,9 @@
 container: "system"
 
 flag {
-     name: "oneway_finalizer_close"
+     name: "oneway_finalizer_close_fixed"
      namespace: "system_performance"
+     is_fixed_read_only: true
      description: "Make BuildCursorNative.close oneway if in the the finalizer"
      bug: "368221351"
 }
diff --git a/core/java/android/hardware/location/OWNERS b/core/java/android/hardware/location/OWNERS
index 747f909..340d6f2 100644
--- a/core/java/android/hardware/location/OWNERS
+++ b/core/java/android/hardware/location/OWNERS
@@ -9,4 +9,4 @@
 yuhany@google.com
 
 # ContextHub team
-per-file *ContextHub*,*NanoApp* = file:platform/system/chre:/OWNERS
+per-file Android.bp,*Hub*,*NanoApp* = file:platform/system/chre:/OWNERS
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index a753f96..37604bc 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,7 +1,7 @@
 # Bug component: 175220
 
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
 anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
 badhri@google.com
+kumarashishg@google.com
\ No newline at end of file
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 48eb968..f7dc790 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -5,13 +5,6 @@
 # Flags used for module APIs must be in aconfig files under each modules
 
 flag {
-  name: "ipsec_transform_state"
-  namespace: "core_networking_ipsec"
-  description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
-  bug: "308011229"
-}
-
-flag {
     name: "powered_off_finding_platform"
     namespace: "nearby"
     description: "Controls whether the Powered Off Finding feature is enabled"
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 30d2dec..3c3a4fd 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -36,8 +36,6 @@
 import android.util.Slog;
 import android.view.View;
 
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
 import dalvik.system.VMRuntime;
 
 import java.util.ArrayList;
@@ -51,10 +49,6 @@
  */
 @RavenwoodKeepWholeClass
 public class Build {
-    static {
-        // Set up the default system properties.
-        RavenwoodEnvironment.ensureRavenwoodInitialized();
-    }
     private static final String TAG = "Build";
 
     /** Value used for when a build property is unknown. */
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index e80efd2..60eeb2b 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -41,7 +41,6 @@
 import android.net.Uri;
 import android.os.MessageQueue.OnFileDescriptorEventListener;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodReplace;
 import android.ravenwood.annotation.RavenwoodThrow;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -51,8 +50,6 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
 import dalvik.system.VMRuntime;
 
 import libcore.io.IoUtils;
@@ -1254,15 +1251,10 @@
         }
     }
 
-    @RavenwoodReplace
     private static boolean isAtLeastQ() {
         return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
     }
 
-    private static boolean isAtLeastQ$ravenwood() {
-        return RavenwoodEnvironment.workaround().isTargetSdkAtLeastQ();
-    }
-
     private static int ifAtLeastQ(int value) {
         return isAtLeastQ() ? value : 0;
     }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index cd8788d..851953a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -29,6 +29,11 @@
 import android.annotation.UptimeMillisLong;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build.VERSION_CODES;
+import android.ravenwood.annotation.RavenwoodKeep;
+import android.ravenwood.annotation.RavenwoodKeepPartialClass;
+import android.ravenwood.annotation.RavenwoodRedirect;
+import android.ravenwood.annotation.RavenwoodRedirectionClass;
+import android.ravenwood.annotation.RavenwoodReplace;
 import android.sysprop.MemoryProperties;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -37,8 +42,6 @@
 import android.util.Pair;
 import android.webkit.WebViewZygote;
 
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.Preconditions;
 import com.android.sdksandbox.flags.Flags;
 
 import dalvik.system.VMDebug;
@@ -55,6 +58,8 @@
 /**
  * Tools for managing OS processes.
  */
+@RavenwoodKeepPartialClass
+@RavenwoodRedirectionClass("Process_ravenwood")
 public class Process {
     private static final String LOG_TAG = "Process";
 
@@ -672,7 +677,6 @@
      */
     public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
 
-
     /**
      * The process name set via {@link #setArgV0(String)}.
      */
@@ -846,47 +850,20 @@
     /**
      * Returns true if the current process is a 64-bit runtime.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean is64Bit() {
         return VMRuntime.getRuntime().is64Bit();
     }
 
-    private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
-
-    /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
-    public static void init$ravenwood(final int uid, final int pid) {
-        sIdentity$ravenwood = ThreadLocal.withInitial(() -> {
-            final SomeArgs args = SomeArgs.obtain();
-            args.argi1 = uid;
-            args.argi2 = pid;
-            args.argi3 = Long.hashCode(Thread.currentThread().getId());
-            args.argi4 = THREAD_PRIORITY_DEFAULT;
-            args.arg1 = Boolean.TRUE; // backgroundOk
-            return args;
-        });
-    }
-
-    /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
-    public static void reset$ravenwood() {
-        sIdentity$ravenwood = null;
-    }
-
     /**
      * Returns the identifier of this process, which can be used with
      * {@link #killProcess} and {@link #sendSignal}.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodKeep
     public static final int myPid() {
         return Os.getpid();
     }
 
-    /** @hide */
-    public static final int myPid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2;
-    }
-
     /**
      * Returns the identifier of this process' parent.
      * @hide
@@ -900,39 +877,29 @@
      * Returns the identifier of the calling thread, which be used with
      * {@link #setThreadPriority(int, int)}.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodKeep
     public static final int myTid() {
         return Os.gettid();
     }
 
-    /** @hide */
-    public static final int myTid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi3;
-    }
-
     /**
      * Returns the identifier of this process's uid.  This is the kernel uid
      * that the process is running under, which is the identity of its
      * app-specific sandbox.  It is different from {@link #myUserHandle} in that
      * a uid identifies a specific app sandbox in a specific user.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodKeep
     public static final int myUid() {
         return Os.getuid();
     }
 
-    /** @hide */
-    public static final int myUid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1;
-    }
-
     /**
      * Returns this process's user handle.  This is the
      * user the process is running under.  It is distinct from
      * {@link #myUid()} in that a particular user will have multiple
      * distinct apps running under it each with their own uid.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static UserHandle myUserHandle() {
         return UserHandle.of(UserHandle.getUserId(myUid()));
     }
@@ -941,7 +908,7 @@
      * Returns whether the given uid belongs to a system core component or not.
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static boolean isCoreUid(int uid) {
         return UserHandle.isCore(uid);
     }
@@ -952,7 +919,7 @@
      * @return Whether the uid corresponds to an application sandbox running in
      *     a specific user.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static boolean isApplicationUid(int uid) {
         return UserHandle.isApp(uid);
     }
@@ -960,7 +927,7 @@
     /**
      * Returns whether the current process is in an isolated sandbox.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isIsolated() {
         return isIsolated(myUid());
     }
@@ -972,7 +939,7 @@
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
             publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isIsolated(int uid) {
         return isIsolatedUid(uid);
     }
@@ -980,7 +947,7 @@
     /**
      * Returns whether the process with the given {@code uid} is an isolated sandbox.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isIsolatedUid(int uid) {
         uid = UserHandle.getAppId(uid);
         return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
@@ -992,7 +959,7 @@
      * @see android.app.sdksandbox.SdkSandboxManager
      */
     @SuppressLint("UnflaggedApi") // promoting from @SystemApi.
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isSdkSandboxUid(int uid) {
         uid = UserHandle.getAppId(uid);
         return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
@@ -1008,7 +975,7 @@
      * @throws IllegalArgumentException if input is not an sdk sandbox uid
      */
     @SuppressLint("UnflaggedApi") // promoting from @SystemApi.
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final int getAppUidForSdkSandboxUid(int uid) {
         if (!isSdkSandboxUid(uid)) {
             throw new IllegalArgumentException("Input UID is not an SDK sandbox UID");
@@ -1024,7 +991,7 @@
      */
     @SystemApi(client = MODULE_LIBRARIES)
     @TestApi
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     // TODO(b/318651609): Deprecate once Process#getSdkSandboxUidForAppUid is rolled out to 100%
     public static final int toSdkSandboxUid(int uid) {
         return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
@@ -1040,7 +1007,7 @@
      * @throws IllegalArgumentException if input is not an app uid
      */
     @FlaggedApi(Flags.FLAG_SDK_SANDBOX_UID_TO_APP_UID_API)
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final int getSdkSandboxUidForAppUid(int uid) {
         if (!isApplicationUid(uid)) {
             throw new IllegalArgumentException("Input UID is not an app UID");
@@ -1051,7 +1018,7 @@
     /**
      * Returns whether the current process is a sdk sandbox process.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isSdkSandbox() {
         return isSdkSandboxUid(myUid());
     }
@@ -1128,28 +1095,11 @@
      * not have permission to modify the given thread, or to use the given
      * priority.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodRedirect
     public static final native void setThreadPriority(int tid,
             @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
             throws IllegalArgumentException, SecurityException;
 
-    /** @hide */
-    public static final void setThreadPriority$ravenwood(int tid, int priority) {
-        final SomeArgs args =
-                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
-        if (args.argi3 == tid) {
-            boolean backgroundOk = (args.arg1 == Boolean.TRUE);
-            if (priority >= THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
-                throw new IllegalArgumentException(
-                        "Priority " + priority + " blocked by setCanSelfBackground()");
-            }
-            args.argi4 = priority;
-        } else {
-            throw new UnsupportedOperationException(
-                    "Cross-thread priority management not yet available in Ravenwood");
-        }
-    }
-
     /**
      * Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to
      * throw an exception if passed a background-level thread priority.  This is only
@@ -1157,16 +1107,9 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodRedirect
     public static final native void setCanSelfBackground(boolean backgroundOk);
 
-    /** @hide */
-    public static final void setCanSelfBackground$ravenwood(boolean backgroundOk) {
-        final SomeArgs args =
-                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
-        args.arg1 = Boolean.valueOf(backgroundOk);
-    }
-
     /**
      * Sets the scheduling group for a thread.
      * @hide
@@ -1295,13 +1238,12 @@
      *
      * @see #setThreadPriority(int, int)
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodReplace
     public static final native void setThreadPriority(
             @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
             throws IllegalArgumentException, SecurityException;
 
-    /** @hide */
-    public static final void setThreadPriority$ravenwood(int priority) {
+    private static void setThreadPriority$ravenwood(int priority) {
         setThreadPriority(myTid(), priority);
     }
 
@@ -1318,23 +1260,11 @@
      * @throws IllegalArgumentException Throws IllegalArgumentException if
      * <var>tid</var> does not exist.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodRedirect
     @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST)
     public static final native int getThreadPriority(int tid)
             throws IllegalArgumentException;
 
-    /** @hide */
-    public static final int getThreadPriority$ravenwood(int tid) {
-        final SomeArgs args =
-                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
-        if (args.argi3 == tid) {
-            return args.argi4;
-        } else {
-            throw new UnsupportedOperationException(
-                    "Cross-thread priority management not yet available in Ravenwood");
-        }
-    }
-
     /**
      * Return the current scheduling policy of a thread, based on Linux.
      *
diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS
index 0809de2..ce79f5d 100644
--- a/core/java/android/print/OWNERS
+++ b/core/java/android/print/OWNERS
@@ -2,3 +2,4 @@
 
 anothermark@google.com
 kumarashishg@google.com
+bmgordon@google.com
diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS
index 0809de2..ce79f5d 100644
--- a/core/java/android/printservice/OWNERS
+++ b/core/java/android/printservice/OWNERS
@@ -2,3 +2,4 @@
 
 anothermark@google.com
 kumarashishg@google.com
+bmgordon@google.com
diff --git a/core/java/android/service/contextualsearch/OWNERS b/core/java/android/service/contextualsearch/OWNERS
index b723872..c435bd8 100644
--- a/core/java/android/service/contextualsearch/OWNERS
+++ b/core/java/android/service/contextualsearch/OWNERS
@@ -1,2 +1,3 @@
 srazdan@google.com
-hackz@google.com
+hyunyoungs@google.com
+awickham@google.com
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 8358b9a..1dd9d46 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -75,8 +75,7 @@
 @android.ravenwood.annotation.RavenwoodClassLoadHook(
         "com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook.onClassLoaded")
 // Uncomment the following annotation to switch to the Java substitution version.
-//@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
-//        "com.android.platform.test.ravenwood.nativesubstitution.Log_host")
+@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_host")
 public final class Log {
     /** @hide */
     @IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE})
@@ -250,6 +249,7 @@
      *         tag limit of concern after this API level.
      */
     @FastNative
+    @android.ravenwood.annotation.RavenwoodRedirect
     public static native boolean isLoggable(@Nullable String tag, @Level int level);
 
     /**
@@ -425,6 +425,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @android.ravenwood.annotation.RavenwoodRedirect
     public static native int println_native(int bufID, int priority, String tag, String msg);
 
     /**
@@ -452,6 +453,7 @@
      * Return the maximum payload the log daemon accepts without truncation.
      * @return LOGGER_ENTRY_MAX_PAYLOAD.
      */
+    @android.ravenwood.annotation.RavenwoodRedirect
     private static native int logger_entry_max_payload_native();
 
     /**
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 69228ca..cd02130 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -157,7 +157,7 @@
 
     /**
      * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
-     * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+     * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
      * moving back past the threshold. This constant indicates that the user's motion has just
      * passed the threshold for the action to be activated on release.
      *
@@ -167,7 +167,7 @@
 
     /**
      * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
-     * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+     * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
      * moving back past the threshold. This constant indicates that the user's motion has just
      * re-crossed back "under" the threshold for the action to be activated, meaning the gesture is
      * currently in a cancelled state.
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 30b160a..a69d2e4 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -28,19 +28,9 @@
 public final class RavenwoodEnvironment {
     public static final String TAG = "RavenwoodEnvironment";
 
-    private static final RavenwoodEnvironment sInstance;
-    private static final Workaround sWorkaround;
+    private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment();
 
-    private RavenwoodEnvironment() {
-    }
-
-    static {
-        sInstance = new RavenwoodEnvironment();
-        sWorkaround = new Workaround();
-        ensureRavenwoodInitialized();
-    }
-
-    public static RuntimeException notSupportedOnDevice() {
+    private static RuntimeException notSupportedOnDevice() {
         return new UnsupportedOperationException("This method can only be used on Ravenwood");
     }
 
@@ -52,15 +42,6 @@
     }
 
     /**
-     * Initialize the ravenwood environment if it hasn't happened already, if running on Ravenwood.
-     *
-     * No-op if called on the device side.
-     */
-    @RavenwoodRedirect
-    public static void ensureRavenwoodInitialized() {
-    }
-
-    /**
      * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment.
      *
      * <p>Using this allows code to behave differently on a real device and on Ravenwood, but
@@ -91,38 +72,10 @@
     }
 
     /**
-     * See {@link Workaround}. It's only usable on Ravenwood.
-     */
-    @RavenwoodReplace
-    public static Workaround workaround() {
-        throw notSupportedOnDevice();
-    }
-
-    private static Workaround workaround$ravenwood() {
-        return sWorkaround;
-    }
-
-    /**
      * @return the "ravenwood-runtime" directory.
      */
     @RavenwoodRedirect
     public String getRavenwoodRuntimePath() {
         throw notSupportedOnDevice();
     }
-
-    /**
-     * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should
-     * be empty, and all its APIs should be able to be implemented properly.
-     */
-    public static class Workaround {
-        Workaround() {
-        }
-
-        /**
-         * @return whether the app's target SDK level is at least Q.
-         */
-        public boolean isTargetSdkAtLeastQ() {
-            return true;
-        }
-    }
 }
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 3a19f46..96edd63 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -23,6 +23,7 @@
 import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
 import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.codec.Flags.FLAG_APV_SUPPORT;
 import static android.media.MediaCodec.GetFlag;
 
 import android.annotation.FlaggedApi;
@@ -4496,6 +4497,265 @@
         @SuppressLint("AllUpper")
         public static final int AC4Level4       = 0x10;
 
+        // Profiles and levels/bands for APV Codec, corresponding to the definitions in
+        // "Advanced Professional Video", 10.1.3 Profiles, 10.1.4 Levels and Bands
+        // found at https://www.ietf.org/archive/id/draft-lim-apv-02.html
+
+        /**
+         * APV codec profile 422-10 as per IETF lim-apv-02, 10.1.3.1.1
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVProfile422_10 =  0x01;
+
+        /**
+         * APV codec profile 422-10 as per IETF lim-apv-02, 10.1.3.1.1
+         * with HDR10.
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVProfile422_10HDR10 =  0x1000;
+
+        /**
+         * APV codec profile 422-10 as per IETF lim-apv-02, 10.1.3.1.1
+         * with HDR10Plus.
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVProfile422_10HDR10Plus =  0x2000;
+
+        // For APV Levels, the numerical values are constructed as follows:
+        //   ((0x100 << (level_num - 1)) | (1 << band))
+        // where:
+        //   - "level_num" is the APV Level numbered consecutively
+        //     (i.e., Level 1 == 1, Level 1.1 == 2, etc.)
+        //   - "band" is the APV Band
+
+        /** APV Codec Level 1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band0 =  0x101;
+        /** APV Codec Level 1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band1 =  0x102;
+        /** APV Codec Level 1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band2 =  0x104;
+        /** APV Codec Level 1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band3 =  0x108;
+        /** APV Codec Level 1.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band0 = 0x201;
+        /** APV Codec Level 1.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band1 = 0x202;
+        /** APV Codec Level 1.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band2 = 0x204;
+        /** APV Codec Level 1.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band3 = 0x208;
+        /** APV Codec Level 2, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band0 =  0x401;
+        /** APV Codec Level 2, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band1 =  0x402;
+        /** APV Codec Level 2, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band2 =  0x404;
+        /** APV Codec Level 2, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band3 =  0x408;
+        /** APV Codec Level 2.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band0 = 0x801;
+        /** APV Codec Level 2.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band1 = 0x802;
+        /** APV Codec Level 2.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band2 = 0x804;
+        /** APV Codec Level 2.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band3 = 0x808;
+        /** APV Codec Level 3, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band0 =  0x1001;
+        /** APV Codec Level 3, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band1 =  0x1002;
+        /** APV Codec Level 3, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band2 =  0x1004;
+        /** APV Codec Level 3, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band3 =  0x1008;
+        /** APV Codec Level 3.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band0 = 0x2001;
+        /** APV Codec Level 3.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band1 = 0x2002;
+        /** APV Codec Level 3.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band2 = 0x2004;
+        /** APV Codec Level 3.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band3 = 0x2008;
+        /** APV Codec Level 4, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band0 =  0x4001;
+        /** APV Codec Level 4, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band1 =  0x4002;
+        /** APV Codec Level 4, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band2 =  0x4004;
+        /** APV Codec Level 4, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band3 =  0x4008;
+        /** APV Codec Level 4.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band0 = 0x8001;
+        /** APV Codec Level 4.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band1 = 0x8002;
+        /** APV Codec Level 4.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band2 = 0x8004;
+        /** APV Codec Level 4.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band3 = 0x8008;
+        /** APV Codec Level 5, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band0 =  0x10001;
+        /** APV Codec Level 5, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band1 =  0x10002;
+        /** APV Codec Level 5, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band2 =  0x10004;
+        /** APV Codec Level 5, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band3 =  0x10008;
+        /** APV Codec Level 5.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band0 = 0x20001;
+        /** APV Codec Level 5.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band1 = 0x20002;
+        /** APV Codec Level 5.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band2 = 0x20004;
+        /** APV Codec Level 5.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band3 = 0x20008;
+        /** APV Codec Level 6, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band0 =  0x40001;
+        /** APV Codec Level 6, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band1 =  0x40002;
+        /** APV Codec Level 6, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band2 =  0x40004;
+        /** APV Codec Level 6, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band3 =  0x40008;
+        /** APV Codec Level 6.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band0 = 0x80001;
+        /** APV Codec Level 6.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band1 = 0x80002;
+        /** APV Codec Level 6.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band2 = 0x80004;
+        /** APV Codec Level 6.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band3 = 0x80008;
+        /** APV Codec Level 7, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band0 =  0x100001;
+        /** APV Codec Level 7, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band1 =  0x100002;
+        /** APV Codec Level 7, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band2 =  0x100004;
+        /** APV Codec Level 7, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band3 =  0x100008;
+        /** APV Codec Level 7.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band0 = 0x200001;
+        /** APV Codec Level 7.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band1 = 0x200002;
+        /** APV Codec Level 7.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band2 = 0x200004;
+        /** APV Codec Level 7.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band3 = 0x200008;
+
         /**
          * The profile of the media content. Depending on the type of media this can be
          * one of the profile values defined in this class.
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index cd0654c..b08a86e 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -18,6 +18,7 @@
 
 import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.codec.Flags.FLAG_APV_SUPPORT;
 
 import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE;
 import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
@@ -157,6 +158,8 @@
 public final class MediaFormat {
     public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
     public static final String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
+    @FlaggedApi(FLAG_APV_SUPPORT)
+    public static final String MIMETYPE_VIDEO_APV = "video/apv";
     public static final String MIMETYPE_VIDEO_AV1 = "video/av01";
     public static final String MIMETYPE_VIDEO_AVC = "video/avc";
     public static final String MIMETYPE_VIDEO_HEVC = "video/hevc";
diff --git a/media/java/android/media/audiofx/Equalizer.java b/media/java/android/media/audiofx/Equalizer.java
index 7abada0..0986345 100644
--- a/media/java/android/media/audiofx/Equalizer.java
+++ b/media/java/android/media/audiofx/Equalizer.java
@@ -153,9 +153,8 @@
             param[0] = PARAM_GET_PRESET_NAME;
             for (int i = 0; i < mNumPresets; i++) {
                 param[1] = i;
-                checkStatus(getParameter(param, value));
-                int length = 0;
-                while (value[length] != 0) length++;
+                final int length = getParameter(param, value);
+                checkStatus(length);
                 try {
                     mPresetNames[i] = new String(value, 0, length, "ISO-8859-1");
                 } catch (java.io.UnsupportedEncodingException e) {
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index 185f579..0adc478 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -15,3 +15,10 @@
   description: "Enable B frames for Stagefright recorder."
   bug: "341121900"
 }
+
+flag {
+  name: "muxer_mp4_enable_apv"
+  namespace: "media_solutions"
+  description: "Enable APV support in mp4 writer."
+  bug: "370061501"
+}
diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS
index 6b5336e..77ed08b 100644
--- a/media/java/android/mtp/OWNERS
+++ b/media/java/android/mtp/OWNERS
@@ -1,10 +1,9 @@
 set noparent
 
-aprasath@google.com
 anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
 jsharkey@android.com
 jameswei@google.com
 rmojumder@google.com
-
+kumarashishg@google.com
diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS
index 6b5336e..bdb6cdb 100644
--- a/media/tests/MtpTests/OWNERS
+++ b/media/tests/MtpTests/OWNERS
@@ -1,10 +1,9 @@
 set noparent
 
-aprasath@google.com
 anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
 jsharkey@android.com
 jameswei@google.com
 rmojumder@google.com
-
+kumarashishg@google.com
\ No newline at end of file
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 96b7c13..00812042 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -207,6 +207,7 @@
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
     method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported();
     method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+    method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
     method public boolean removeAidsForService(android.content.ComponentName, String);
@@ -216,6 +217,7 @@
     method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
     method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
     method public boolean supportsAidPrefixRegistration();
+    method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventListener(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
     method public boolean unsetPreferredService(android.app.Activity);
     field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
@@ -233,13 +235,16 @@
     field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
   }
 
+  @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventListener {
+    method @FlaggedApi("android.nfc.nfc_event_listener") public default void onObserveModeStateChanged(boolean);
+    method @FlaggedApi("android.nfc.nfc_event_listener") public default void onPreferredServiceChanged(boolean);
+  }
+
   public abstract class HostApduService extends android.app.Service {
     ctor public HostApduService();
     method public final void notifyUnhandled();
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onDeactivated(int);
-    method @FlaggedApi("android.nfc.nfc_event_listener") public void onObserveModeStateChanged(boolean);
-    method @FlaggedApi("android.nfc.nfc_event_listener") public void onPreferredServiceChanged(boolean);
     method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.nfc.cardemulation.PollingFrame>);
     method public final void sendResponseApdu(byte[]);
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 24e14e6..6aa8a2b 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -91,6 +91,7 @@
     method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onDisableFinished(int);
     method public void onDisableStarted();
+    method public void onEeListenActivated(boolean);
     method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onEnableFinished(int);
     method public void onEnableStarted();
@@ -105,7 +106,7 @@
     method public void onRfFieldActivated(boolean);
     method public void onRoutingChanged();
     method public void onStateUpdated(int);
-    method public void onTagConnected(boolean, @NonNull android.nfc.Tag);
+    method public void onTagConnected(boolean);
     method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
   }
 
diff --git a/nfc/java/android/nfc/ComponentNameAndUser.aidl b/nfc/java/android/nfc/ComponentNameAndUser.aidl
new file mode 100644
index 0000000..e677998
--- /dev/null
+++ b/nfc/java/android/nfc/ComponentNameAndUser.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.nfc;
+
+parcelable ComponentNameAndUser;
\ No newline at end of file
diff --git a/nfc/java/android/nfc/ComponentNameAndUser.java b/nfc/java/android/nfc/ComponentNameAndUser.java
new file mode 100644
index 0000000..59e6c62
--- /dev/null
+++ b/nfc/java/android/nfc/ComponentNameAndUser.java
@@ -0,0 +1,100 @@
+/*
+ * 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.nfc;
+
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public class ComponentNameAndUser implements Parcelable {
+    @UserIdInt private final int mUserId;
+    private ComponentName mComponentName;
+
+    public ComponentNameAndUser(@UserIdInt int userId, ComponentName componentName) {
+        mUserId = userId;
+        mComponentName = componentName;
+    }
+
+    /**
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mUserId);
+        out.writeParcelable(mComponentName, flags);
+    }
+
+    public static final Parcelable.Creator<ComponentNameAndUser> CREATOR =
+            new Parcelable.Creator<ComponentNameAndUser>() {
+                public ComponentNameAndUser createFromParcel(Parcel in) {
+                    return new ComponentNameAndUser(in);
+                }
+
+                public ComponentNameAndUser[] newArray(int size) {
+                    return new ComponentNameAndUser[size];
+                }
+            };
+
+    private ComponentNameAndUser(Parcel in) {
+        mUserId = in.readInt();
+        mComponentName = in.readParcelable(null, ComponentName.class);
+    }
+
+    @UserIdInt
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    @Override
+    public String toString() {
+        return mComponentName + " for user id: " + mUserId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj != null && obj instanceof ComponentNameAndUser) {
+            ComponentNameAndUser other = (ComponentNameAndUser) obj;
+            return other.getUserId() == mUserId
+                    && Objects.equals(other.getComponentName(), mComponentName);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        if (mComponentName == null) {
+            return mUserId;
+        }
+        return mComponentName.hashCode() + mUserId;
+    }
+}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 8535e4a..5e2e92d 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -17,6 +17,8 @@
 package android.nfc;
 
 import android.content.ComponentName;
+import android.nfc.INfcEventListener;
+
 import android.nfc.cardemulation.AidGroup;
 import android.nfc.cardemulation.ApduServiceInfo;
 import android.os.RemoteCallback;
@@ -55,4 +57,7 @@
     boolean isAutoChangeEnabled();
     List<String> getRoutingStatus();
     void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc);
+
+    void registerNfcEventListener(in INfcEventListener listener);
+    void unregisterNfcEventListener(in INfcEventListener listener);
 }
diff --git a/nfc/java/android/nfc/INfcEventListener.aidl b/nfc/java/android/nfc/INfcEventListener.aidl
new file mode 100644
index 0000000..5162c26
--- /dev/null
+++ b/nfc/java/android/nfc/INfcEventListener.aidl
@@ -0,0 +1,11 @@
+package android.nfc;
+
+import android.nfc.ComponentNameAndUser;
+
+/**
+ * @hide
+ */
+oneway interface INfcEventListener {
+    void onPreferredServiceChanged(in ComponentNameAndUser ComponentNameAndUser);
+    void onObserveModeStateChanged(boolean isEnabled);
+}
\ No newline at end of file
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index 48c7ee6..7f1fd15 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -27,7 +27,7 @@
  * @hide
  */
 interface INfcOemExtensionCallback {
-   void onTagConnected(boolean connected, in Tag tag);
+   void onTagConnected(boolean connected);
    void onStateUpdated(int state);
    void onApplyRouting(in ResultReceiver isSkipped);
    void onNdefRead(in ResultReceiver isSkipped);
@@ -46,6 +46,7 @@
    void onCardEmulationActivated(boolean isActivated);
    void onRfFieldActivated(boolean isActivated);
    void onRfDiscoveryStarted(boolean isDiscoveryStarted);
+   void onEeListenActivated(boolean isActivated);
    void onGetOemAppSearchIntent(in List<String> firstPackage, in ResultReceiver intentConsumer);
    void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
    void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 520ba89..1d2085c 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -80,6 +80,14 @@
     private boolean mCardEmulationActivated = false;
     private boolean mRfFieldActivated = false;
     private boolean mRfDiscoveryStarted = false;
+    private boolean mEeListenActivated = false;
+
+    /**
+     * Broadcast Action: Sent on NFC stack initialization when NFC OEM extensions are enabled.
+     * <p> OEM extension modules should use this intent to start their extension service </p>
+     * @hide
+     */
+    public static final String ACTION_OEM_EXTENSION_INIT = "android.nfc.action.OEM_EXTENSION_INIT";
 
     /**
      * Mode Type for {@link #setControllerAlwaysOnMode(int)}.
@@ -188,9 +196,8 @@
          * ex - if tag is connected  notify cover and Nfctest app if app is in testing mode
          *
          * @param connected status of the tag true if tag is connected otherwise false
-         * @param tag Tag details
          */
-        void onTagConnected(boolean connected, @NonNull Tag tag);
+        void onTagConnected(boolean connected);
 
         /**
          * Update the Nfc Adapter State
@@ -320,6 +327,13 @@
         void onRfDiscoveryStarted(boolean isDiscoveryStarted);
 
         /**
+        * Notifies the NFCEE (NFC Execution Environment) Listen has been activated.
+        *
+        * @param isActivated true, if EE Listen is ON, else EE Listen is OFF.
+        */
+        void onEeListenActivated(boolean isActivated);
+
+        /**
          * Gets the intent to find the OEM package in the OEM App market. If the consumer returns
          * {@code null} or a timeout occurs, the intent from the first available package will be
          * used instead.
@@ -430,6 +444,7 @@
                 callback.onCardEmulationActivated(mCardEmulationActivated);
                 callback.onRfFieldActivated(mRfFieldActivated);
                 callback.onRfDiscoveryStarted(mRfDiscoveryStarted);
+                callback.onEeListenActivated(mEeListenActivated);
             });
         }
     }
@@ -677,9 +692,9 @@
     private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
 
         @Override
-        public void onTagConnected(boolean connected, Tag tag) throws RemoteException {
+        public void onTagConnected(boolean connected) throws RemoteException {
             mCallbackMap.forEach((cb, ex) ->
-                    handleVoid2ArgCallback(connected, tag, cb::onTagConnected, ex));
+                    handleVoidCallback(connected, cb::onTagConnected, ex));
         }
 
         @Override
@@ -704,6 +719,13 @@
         }
 
         @Override
+        public void onEeListenActivated(boolean isActivated) throws RemoteException {
+            mEeListenActivated = isActivated;
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(isActivated, cb::onEeListenActivated, ex));
+        }
+
+        @Override
         public void onStateUpdated(int state) throws RemoteException {
             mCallbackMap.forEach((cb, ex) ->
                     handleVoidCallback(state, cb::onStateUpdated, ex));
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 5727f99..3d293f7 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,10 +52,12 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.TreeMap;
 import java.util.regex.Pattern;
 
 /**
@@ -181,7 +183,8 @@
         this(info, onHost, description, staticAidGroups, dynamicAidGroups,
                 requiresUnlock, requiresScreenOn, bannerResource, uid,
                 settingsActivityName, offHost, staticOffHost, isEnabled,
-                new HashMap<String, Boolean>(), new HashMap<Pattern, Boolean>());
+                new HashMap<String, Boolean>(), new TreeMap<>(
+                        Comparator.comparing(Pattern::toString)));
     }
 
     /**
@@ -317,7 +320,8 @@
             mStaticAidGroups = new HashMap<String, AidGroup>();
             mDynamicAidGroups = new HashMap<String, AidGroup>();
             mAutoTransact = new HashMap<String, Boolean>();
-            mAutoTransactPatterns = new HashMap<Pattern, Boolean>();
+            mAutoTransactPatterns = new TreeMap<Pattern, Boolean>(
+                    Comparator.comparing(Pattern::toString));
             mOnHost = onHost;
 
             final int depth = parser.getDepth();
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index d8f04c5..eb28c3b 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -17,6 +17,7 @@
 package android.nfc.cardemulation;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -33,15 +34,18 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.nfc.ComponentNameAndUser;
 import android.nfc.Constants;
 import android.nfc.Flags;
 import android.nfc.INfcCardEmulation;
+import android.nfc.INfcEventListener;
 import android.nfc.NfcAdapter;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -50,6 +54,8 @@
 import java.util.HexFormat;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Executor;
 import java.util.regex.Pattern;
 
 /**
@@ -1076,4 +1082,107 @@
             default -> throw new IllegalStateException("Unexpected value: " + route);
         };
     }
+
+    /** Listener for preferred service state changes. */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+    public interface NfcEventListener {
+        /**
+         * This method is called when this package gains or loses preferred Nfc service status,
+         * either the Default Wallet Role holder (see {@link
+         * android.app.role.RoleManager#ROLE_WALLET}) or the preferred service of the foreground
+         * activity set with {@link #setPreferredService(Activity, ComponentName)}
+         *
+         * @param isPreferred true is this service has become the preferred Nfc service, false if it
+         *     is no longer the preferred service
+         */
+        @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+        default void onPreferredServiceChanged(boolean isPreferred) {}
+
+        /**
+         * This method is called when observe mode has been enabled or disabled.
+         *
+         * @param isEnabled true if observe mode has been enabled, false if it has been disabled
+         */
+        @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+        default void onObserveModeStateChanged(boolean isEnabled) {}
+    }
+
+    private final ArrayMap<NfcEventListener, Executor> mNfcEventListeners = new ArrayMap<>();
+
+    final INfcEventListener mINfcEventListener =
+            new INfcEventListener.Stub() {
+                public void onPreferredServiceChanged(ComponentNameAndUser componentNameAndUser) {
+                    if (!android.nfc.Flags.nfcEventListener()) {
+                        return;
+                    }
+                    boolean isPreferred =
+                            componentNameAndUser != null
+                                    && componentNameAndUser.getUserId()
+                                            == mContext.getUser().getIdentifier()
+                                    && componentNameAndUser.getComponentName() != null
+                                    && Objects.equals(
+                                            mContext.getPackageName(),
+                                            componentNameAndUser.getComponentName()
+                                                    .getPackageName());
+                    synchronized (mNfcEventListeners) {
+                        mNfcEventListeners.forEach(
+                                (listener, executor) -> {
+                                    executor.execute(
+                                            () -> listener.onPreferredServiceChanged(isPreferred));
+                                });
+                    }
+                }
+
+                public void onObserveModeStateChanged(boolean isEnabled) {
+                    if (!android.nfc.Flags.nfcEventListener()) {
+                        return;
+                    }
+                    synchronized (mNfcEventListeners) {
+                        mNfcEventListeners.forEach(
+                                (listener, executor) -> {
+                                    executor.execute(
+                                            () -> listener.onObserveModeStateChanged(isEnabled));
+                                });
+                    }
+                }
+            };
+
+    /**
+     * Register a listener for NFC Events.
+     *
+     * @param executor The Executor to run the call back with
+     * @param listener The listener to register
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+    public void registerNfcEventListener(
+            @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventListener listener) {
+        if (!android.nfc.Flags.nfcEventListener()) {
+            return;
+        }
+        synchronized (mNfcEventListeners) {
+            mNfcEventListeners.put(listener, executor);
+            if (mNfcEventListeners.size() == 1) {
+                callService(() -> sService.registerNfcEventListener(mINfcEventListener));
+            }
+        }
+    }
+
+    /**
+     * Unregister a preferred service listener that was previously registered with {@link
+     * #registerNfcEventListener(Executor, NfcEventListener)}
+     *
+     * @param listener The previously registered listener to unregister
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+    public void unregisterNfcEventListener(@NonNull NfcEventListener listener) {
+        if (!android.nfc.Flags.nfcEventListener()) {
+            return;
+        }
+        synchronized (mNfcEventListeners) {
+            mNfcEventListeners.remove(listener);
+            if (mNfcEventListeners.size() == 0) {
+                callService(() -> sService.unregisterNfcEventListener(mINfcEventListener));
+            }
+        }
+    }
 }
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index cd8e19c..4f601f0 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -239,15 +239,6 @@
      */
     public static final int MSG_POLLING_LOOP = 4;
 
-    /**
-     * @hide
-     */
-    public static final int MSG_OBSERVE_MODE_CHANGE = 5;
-
-    /**
-     * @hide
-     */
-    public static final int MSG_PREFERRED_SERVICE_CHANGED = 6;
 
     /**
      * @hide
@@ -343,16 +334,6 @@
                         processPollingFrames(pollingFrames);
                     }
                     break;
-                case MSG_OBSERVE_MODE_CHANGE:
-                    if (android.nfc.Flags.nfcEventListener()) {
-                        onObserveModeStateChanged(msg.arg1 == 1);
-                    }
-                    break;
-                case MSG_PREFERRED_SERVICE_CHANGED:
-                    if (android.nfc.Flags.nfcEventListener()) {
-                        onPreferredServiceChanged(msg.arg1 == 1);
-                    }
-                    break;
                 default:
                 super.handleMessage(msg);
             }
@@ -462,25 +443,4 @@
      */
     public abstract void onDeactivated(int reason);
 
-
-    /**
-     * This method is called when this service is the preferred Nfc service and
-     * Observe mode has been enabled or disabled.
-     *
-     * @param isEnabled true if observe mode has been enabled, false if it has been disabled
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
-    public void onObserveModeStateChanged(boolean isEnabled) {
-
-    }
-
-    /**
-     * This method is called when this service gains or loses preferred Nfc service status.
-     *
-     * @param isPreferred true is this service has become the preferred Nfc service,
-     * false if it is no longer the preferred service
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
-    public void onPreferredServiceChanged(boolean isPreferred) {
-    }
 }
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 6a7e693..34f0200 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -165,3 +165,11 @@
     description: "Enabling security log for nfc state change"
     bug: "319934052"
 }
+
+flag {
+    name: "nfc_associated_role_services"
+    is_exported: true
+    namespace: "nfc"
+    description: "Share wallet role routing priority with associated services"
+    bug: "366243361"
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS
new file mode 100644
index 0000000..a2001e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/accessibility/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 3492365..b2fcc43 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -45,7 +45,7 @@
 
     /** See [registerCriticalDumpable]. */
     fun registerCriticalDumpable(module: Dumpable) {
-        registerCriticalDumpable(module::class.java.canonicalName, module)
+        registerCriticalDumpable(module::class.java.name, module)
     }
 
     /**
@@ -62,7 +62,7 @@
 
     /** See [registerNormalDumpable]. */
     fun registerNormalDumpable(module: Dumpable) {
-        registerNormalDumpable(module::class.java.canonicalName, module)
+        registerNormalDumpable(module::class.java.name, module)
     }
 
     /**
@@ -104,13 +104,10 @@
         dumpables[name] = DumpableEntry(module, name, priority)
     }
 
-    /**
-     * Same as the above override, but automatically uses the canonical class name as the dumpable
-     * name.
-     */
+    /** Same as the above override, but automatically uses the class name as the dumpable name. */
     @Synchronized
     fun registerDumpable(module: Dumpable) {
-        registerDumpable(module::class.java.canonicalName, module)
+        registerDumpable(module::class.java.name, module)
     }
 
     /** Unregisters a previously-registered dumpable. */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
index 6d5226f..08881a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.table.TableLogBuffer
 import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
@@ -131,6 +132,21 @@
     }
 
     @Test
+    fun registerDumpable_supportsAnonymousDumpables() {
+        val anonDumpable =
+            object : Dumpable {
+                override fun dump(pw: PrintWriter, args: Array<out String>) {
+                    pw.println("AnonDumpable")
+                }
+            }
+
+        // THEN registration with implicit names should succeed
+        dumpManager.registerCriticalDumpable(anonDumpable)
+
+        // No exception thrown
+    }
+
+    @Test
     fun getDumpables_returnsSafeCollection() {
         // GIVEN a variety of registered dumpables
         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
diff --git a/packages/overlays/Android.bp b/packages/overlays/Android.bp
index 5075f63..44abb9f 100644
--- a/packages/overlays/Android.bp
+++ b/packages/overlays/Android.bp
@@ -21,6 +21,7 @@
 
 phony {
     name: "frameworks-base-overlays",
+    product_specific: true,
     required: [
         "DisplayCutoutEmulationCornerOverlay",
         "DisplayCutoutEmulationDoubleOverlay",
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 65ea9fe..4731cfb 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -279,6 +279,15 @@
     shared_libs: [
         "liblog",
     ],
+    visibility: ["//visibility:private"],
+}
+
+cc_library_host_shared {
+    name: "libravenwood_initializer",
+    defaults: ["ravenwood_jni_defaults"],
+    srcs: [
+        "runtime-jni/ravenwood_initializer.cpp",
+    ],
 }
 
 // We need this as a separate library because we need to overload the
@@ -301,7 +310,6 @@
         "libutils",
         "libcutils",
     ],
-    visibility: ["//frameworks/base"],
 }
 
 // For collecting the *stats.csv files in a known directory under out/host/linux-x86/testcases/.
@@ -360,6 +368,239 @@
     ],
 }
 
+filegroup {
+    name: "ravenwood-data",
+    device_common_srcs: [
+        ":system-build.prop",
+        ":framework-res",
+        ":ravenwood-empty-res",
+        ":framework-platform-compat-config",
+        ":services-platform-compat-config",
+    ],
+    device_first_srcs: [
+        ":apex_icu.dat",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// Keep in sync with build/make/target/product/generic/Android.bp
+filegroup {
+    name: "ravenwood-fonts",
+    device_common_srcs: [
+        ":AndroidClock.ttf",
+        ":CarroisGothicSC-Regular.ttf",
+        ":ComingSoon.ttf",
+        ":CutiveMono.ttf",
+        ":DancingScript-Regular.ttf",
+        ":DroidSansMono.ttf",
+        ":NotoColorEmoji.ttf",
+        ":NotoColorEmojiFlags.ttf",
+        ":NotoNaskhArabic-Bold.ttf",
+        ":NotoNaskhArabic-Regular.ttf",
+        ":NotoNaskhArabicUI-Bold.ttf",
+        ":NotoNaskhArabicUI-Regular.ttf",
+        ":NotoSansAdlam-VF.ttf",
+        ":NotoSansAhom-Regular.otf",
+        ":NotoSansAnatolianHieroglyphs-Regular.otf",
+        ":NotoSansArmenian-VF.ttf",
+        ":NotoSansAvestan-Regular.ttf",
+        ":NotoSansBalinese-Regular.ttf",
+        ":NotoSansBamum-Regular.ttf",
+        ":NotoSansBassaVah-Regular.otf",
+        ":NotoSansBatak-Regular.ttf",
+        ":NotoSansBengali-VF.ttf",
+        ":NotoSansBengaliUI-VF.ttf",
+        ":NotoSansBhaiksuki-Regular.otf",
+        ":NotoSansBrahmi-Regular.ttf",
+        ":NotoSansBuginese-Regular.ttf",
+        ":NotoSansBuhid-Regular.ttf",
+        ":NotoSansCJK-Regular.ttc",
+        ":NotoSansCanadianAboriginal-Regular.ttf",
+        ":NotoSansCarian-Regular.ttf",
+        ":NotoSansChakma-Regular.otf",
+        ":NotoSansCham-Bold.ttf",
+        ":NotoSansCham-Regular.ttf",
+        ":NotoSansCherokee-Regular.ttf",
+        ":NotoSansCoptic-Regular.ttf",
+        ":NotoSansCuneiform-Regular.ttf",
+        ":NotoSansCypriot-Regular.ttf",
+        ":NotoSansDeseret-Regular.ttf",
+        ":NotoSansDevanagari-VF.ttf",
+        ":NotoSansDevanagariUI-VF.ttf",
+        ":NotoSansEgyptianHieroglyphs-Regular.ttf",
+        ":NotoSansElbasan-Regular.otf",
+        ":NotoSansEthiopic-VF.ttf",
+        ":NotoSansGeorgian-VF.ttf",
+        ":NotoSansGlagolitic-Regular.ttf",
+        ":NotoSansGothic-Regular.ttf",
+        ":NotoSansGrantha-Regular.ttf",
+        ":NotoSansGujarati-Bold.ttf",
+        ":NotoSansGujarati-Regular.ttf",
+        ":NotoSansGujaratiUI-Bold.ttf",
+        ":NotoSansGujaratiUI-Regular.ttf",
+        ":NotoSansGunjalaGondi-Regular.otf",
+        ":NotoSansGurmukhi-VF.ttf",
+        ":NotoSansGurmukhiUI-VF.ttf",
+        ":NotoSansHanifiRohingya-Regular.otf",
+        ":NotoSansHanunoo-Regular.ttf",
+        ":NotoSansHatran-Regular.otf",
+        ":NotoSansHebrew-Bold.ttf",
+        ":NotoSansHebrew-Regular.ttf",
+        ":NotoSansImperialAramaic-Regular.ttf",
+        ":NotoSansInscriptionalPahlavi-Regular.ttf",
+        ":NotoSansInscriptionalParthian-Regular.ttf",
+        ":NotoSansJavanese-Regular.otf",
+        ":NotoSansKaithi-Regular.ttf",
+        ":NotoSansKannada-VF.ttf",
+        ":NotoSansKannadaUI-VF.ttf",
+        ":NotoSansKayahLi-Regular.ttf",
+        ":NotoSansKharoshthi-Regular.ttf",
+        ":NotoSansKhmer-VF.ttf",
+        ":NotoSansKhmerUI-Bold.ttf",
+        ":NotoSansKhmerUI-Regular.ttf",
+        ":NotoSansKhojki-Regular.otf",
+        ":NotoSansLao-Bold.ttf",
+        ":NotoSansLao-Regular.ttf",
+        ":NotoSansLaoUI-Bold.ttf",
+        ":NotoSansLaoUI-Regular.ttf",
+        ":NotoSansLepcha-Regular.ttf",
+        ":NotoSansLimbu-Regular.ttf",
+        ":NotoSansLinearA-Regular.otf",
+        ":NotoSansLinearB-Regular.ttf",
+        ":NotoSansLisu-Regular.ttf",
+        ":NotoSansLycian-Regular.ttf",
+        ":NotoSansLydian-Regular.ttf",
+        ":NotoSansMalayalam-VF.ttf",
+        ":NotoSansMalayalamUI-VF.ttf",
+        ":NotoSansMandaic-Regular.ttf",
+        ":NotoSansManichaean-Regular.otf",
+        ":NotoSansMarchen-Regular.otf",
+        ":NotoSansMasaramGondi-Regular.otf",
+        ":NotoSansMedefaidrin-VF.ttf",
+        ":NotoSansMeeteiMayek-Regular.ttf",
+        ":NotoSansMeroitic-Regular.otf",
+        ":NotoSansMiao-Regular.otf",
+        ":NotoSansModi-Regular.ttf",
+        ":NotoSansMongolian-Regular.ttf",
+        ":NotoSansMro-Regular.otf",
+        ":NotoSansMultani-Regular.otf",
+        ":NotoSansMyanmar-Bold.otf",
+        ":NotoSansMyanmar-Medium.otf",
+        ":NotoSansMyanmar-Regular.otf",
+        ":NotoSansMyanmarUI-Bold.otf",
+        ":NotoSansMyanmarUI-Medium.otf",
+        ":NotoSansMyanmarUI-Regular.otf",
+        ":NotoSansNKo-Regular.ttf",
+        ":NotoSansNabataean-Regular.otf",
+        ":NotoSansNewTaiLue-Regular.ttf",
+        ":NotoSansNewa-Regular.otf",
+        ":NotoSansOgham-Regular.ttf",
+        ":NotoSansOlChiki-Regular.ttf",
+        ":NotoSansOldItalic-Regular.ttf",
+        ":NotoSansOldNorthArabian-Regular.otf",
+        ":NotoSansOldPermic-Regular.otf",
+        ":NotoSansOldPersian-Regular.ttf",
+        ":NotoSansOldSouthArabian-Regular.ttf",
+        ":NotoSansOldTurkic-Regular.ttf",
+        ":NotoSansOriya-Bold.ttf",
+        ":NotoSansOriya-Regular.ttf",
+        ":NotoSansOriyaUI-Bold.ttf",
+        ":NotoSansOriyaUI-Regular.ttf",
+        ":NotoSansOsage-Regular.ttf",
+        ":NotoSansOsmanya-Regular.ttf",
+        ":NotoSansPahawhHmong-Regular.otf",
+        ":NotoSansPalmyrene-Regular.otf",
+        ":NotoSansPauCinHau-Regular.otf",
+        ":NotoSansPhagsPa-Regular.ttf",
+        ":NotoSansPhoenician-Regular.ttf",
+        ":NotoSansRejang-Regular.ttf",
+        ":NotoSansRunic-Regular.ttf",
+        ":NotoSansSamaritan-Regular.ttf",
+        ":NotoSansSaurashtra-Regular.ttf",
+        ":NotoSansSharada-Regular.otf",
+        ":NotoSansShavian-Regular.ttf",
+        ":NotoSansSinhala-VF.ttf",
+        ":NotoSansSinhalaUI-VF.ttf",
+        ":NotoSansSoraSompeng-Regular.otf",
+        ":NotoSansSoyombo-VF.ttf",
+        ":NotoSansSundanese-Regular.ttf",
+        ":NotoSansSylotiNagri-Regular.ttf",
+        ":NotoSansSymbols-Regular-Subsetted.ttf",
+        ":NotoSansSymbols-Regular-Subsetted2.ttf",
+        ":NotoSansSyriacEastern-Regular.ttf",
+        ":NotoSansSyriacEstrangela-Regular.ttf",
+        ":NotoSansSyriacWestern-Regular.ttf",
+        ":NotoSansTagalog-Regular.ttf",
+        ":NotoSansTagbanwa-Regular.ttf",
+        ":NotoSansTaiLe-Regular.ttf",
+        ":NotoSansTaiTham-Regular.ttf",
+        ":NotoSansTaiViet-Regular.ttf",
+        ":NotoSansTakri-VF.ttf",
+        ":NotoSansTamil-VF.ttf",
+        ":NotoSansTamilUI-VF.ttf",
+        ":NotoSansTelugu-VF.ttf",
+        ":NotoSansTeluguUI-VF.ttf",
+        ":NotoSansThaana-Bold.ttf",
+        ":NotoSansThaana-Regular.ttf",
+        ":NotoSansThai-Bold.ttf",
+        ":NotoSansThai-Regular.ttf",
+        ":NotoSansThaiUI-Bold.ttf",
+        ":NotoSansThaiUI-Regular.ttf",
+        ":NotoSansTifinagh-Regular.otf",
+        ":NotoSansUgaritic-Regular.ttf",
+        ":NotoSansVai-Regular.ttf",
+        ":NotoSansWancho-Regular.otf",
+        ":NotoSansWarangCiti-Regular.otf",
+        ":NotoSansYi-Regular.ttf",
+        ":NotoSerif-Bold.ttf",
+        ":NotoSerif-BoldItalic.ttf",
+        ":NotoSerif-Italic.ttf",
+        ":NotoSerif-Regular.ttf",
+        ":NotoSerifArmenian-VF.ttf",
+        ":NotoSerifBengali-VF.ttf",
+        ":NotoSerifCJK-Regular.ttc",
+        ":NotoSerifDevanagari-VF.ttf",
+        ":NotoSerifDogra-Regular.ttf",
+        ":NotoSerifEthiopic-VF.ttf",
+        ":NotoSerifGeorgian-VF.ttf",
+        ":NotoSerifGujarati-VF.ttf",
+        ":NotoSerifGurmukhi-VF.ttf",
+        ":NotoSerifHebrew-Bold.ttf",
+        ":NotoSerifHebrew-Regular.ttf",
+        ":NotoSerifHentaigana.ttf",
+        ":NotoSerifKannada-VF.ttf",
+        ":NotoSerifKhmer-Bold.otf",
+        ":NotoSerifKhmer-Regular.otf",
+        ":NotoSerifLao-Bold.ttf",
+        ":NotoSerifLao-Regular.ttf",
+        ":NotoSerifMalayalam-VF.ttf",
+        ":NotoSerifMyanmar-Bold.otf",
+        ":NotoSerifMyanmar-Regular.otf",
+        ":NotoSerifNyiakengPuachueHmong-VF.ttf",
+        ":NotoSerifSinhala-VF.ttf",
+        ":NotoSerifTamil-VF.ttf",
+        ":NotoSerifTelugu-VF.ttf",
+        ":NotoSerifThai-Bold.ttf",
+        ":NotoSerifThai-Regular.ttf",
+        ":NotoSerifTibetan-VF.ttf",
+        ":NotoSerifYezidi-VF.ttf",
+        ":Roboto-Regular.ttf",
+        ":RobotoFlex-Regular.ttf",
+        ":RobotoStatic-Regular.ttf",
+        ":SourceSansPro-Bold.ttf",
+        ":SourceSansPro-BoldItalic.ttf",
+        ":SourceSansPro-Italic.ttf",
+        ":SourceSansPro-Regular.ttf",
+        ":SourceSansPro-SemiBold.ttf",
+        ":SourceSansPro-SemiBoldItalic.ttf",
+    ],
+    device_first_srcs: [
+        ":font_fallback.xml",
+        ":fonts.xml",
+    ],
+    visibility: ["//visibility:private"],
+}
+
 // JARs in "ravenwood-runtime" are set to the classpath, sorted alphabetically.
 // Rename some of the dependencies to make sure they're included in the intended order.
 
@@ -386,13 +627,8 @@
 
 android_ravenwood_libgroup {
     name: "ravenwood-runtime",
-    data: [
-        ":system-build.prop",
-        ":framework-res",
-        ":ravenwood-empty-res",
-        ":framework-platform-compat-config",
-        ":services-platform-compat-config",
-    ],
+    data: [":ravenwood-data"],
+    fonts: [":ravenwood-fonts"],
     libs: [
         "100-framework-minus-apex.ravenwood",
         "200-kxml2-android",
@@ -431,6 +667,7 @@
     ],
     jni_libs: [
         // Libraries has to be loaded in the following order
+        "libravenwood_initializer",
         "libravenwood_sysprop",
         "libravenwood_runtime",
         "libandroid_runtime",
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 66a6890..869d854 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -26,12 +26,10 @@
 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;
@@ -171,10 +169,11 @@
         final var notifier = new RavenwoodRunNotifier(realNotifier);
         final var description = getDescription();
 
+        RavenwoodTestStats.getInstance().attachToRunNotifier(notifier);
+
         if (mRealRunner instanceof ClassSkippingTestRunner) {
-            mRealRunner.run(notifier);
             Log.i(TAG, "onClassSkipped: description=" + description);
-            RavenwoodTestStats.getInstance().onClassSkipped(description);
+            mRealRunner.run(notifier);
             return;
         }
 
@@ -205,7 +204,6 @@
 
             if (!skipRunnerHook) {
                 try {
-                    RavenwoodTestStats.getInstance().onClassFinished(description);
                     mState.exitTestClass();
                 } catch (Throwable th) {
                     notifier.reportAfterTestFailure(th);
@@ -295,8 +293,6 @@
         // 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;
             }
         }
@@ -317,16 +313,6 @@
             // 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.
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
index d29b93c..a208d6d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
@@ -40,7 +40,7 @@
      * See frameworks/base/core/jni/platform/host/HostRuntime.cpp
      */
     private static final Class<?>[] sLibandroidClasses = {
-            android.util.Log.class,
+//            android.util.Log.class, // Not using native log: b/377377826
             android.os.Parcel.class,
             android.os.Binder.class,
             android.os.SystemProperties.class,
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 9002e40..9177857 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -40,6 +40,7 @@
 import android.os.Bundle;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.Process_ravenwood;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.provider.DeviceConfig_host;
@@ -50,8 +51,10 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.hoststubgen.hosthelper.HostTestUtils;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.RuntimeInit;
 import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
 import com.android.ravenwood.common.RavenwoodCommonUtils;
 import com.android.ravenwood.common.RavenwoodRuntimeException;
 import com.android.ravenwood.common.SneakyThrow;
@@ -84,6 +87,7 @@
     }
 
     private static final String MAIN_THREAD_NAME = "RavenwoodMain";
+    private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
     private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop";
     private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
     private static final String RAVENWOOD_BUILD_PROP =
@@ -137,23 +141,61 @@
         return res;
     }
 
+    private static final Object sInitializationLock = new Object();
+
+    @GuardedBy("sInitializationLock")
+    private static boolean sInitialized = false;
+
+    @GuardedBy("sInitializationLock")
+    private static Throwable sExceptionFromGlobalInit;
+
     private static RavenwoodAwareTestRunner sRunner;
     private static RavenwoodSystemProperties sProps;
-    private static boolean sInitialized = false;
 
     /**
      * Initialize the global environment.
      */
     public static void globalInitOnce() {
-        if (sInitialized) {
-            return;
+        synchronized (sInitializationLock) {
+            if (!sInitialized) {
+                // globalInitOnce() is called from class initializer, which cause
+                // this method to be called recursively,
+                sInitialized = true;
+
+                // This is the first call.
+                try {
+                    globalInitInner();
+                } catch (Throwable th) {
+                    Log.e(TAG, "globalInit() failed", th);
+
+                    sExceptionFromGlobalInit = th;
+                    throw th;
+                }
+            } else {
+                // Subsequent calls. If the first call threw, just throw the same error, to prevent
+                // the test from running.
+                if (sExceptionFromGlobalInit != null) {
+                    Log.e(TAG, "globalInit() failed re-throwing the same exception",
+                            sExceptionFromGlobalInit);
+
+                    SneakyThrow.sneakyThrow(sExceptionFromGlobalInit);
+                }
+            }
         }
-        sInitialized = true;
+    }
+
+    private static void globalInitInner() {
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            Log.v(TAG, "globalInit() called here...", new RuntimeException("NOT A CRASH"));
+        }
+
+        // Some process-wide initialization. (maybe redirect stdout/stderr)
+        RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME);
 
         // We haven't initialized liblog yet, so directly write to System.out here.
-        RavenwoodCommonUtils.log(TAG, "globalInit()");
+        RavenwoodCommonUtils.log(TAG, "globalInitInner()");
 
-        // Load libravenwood_sysprop first
+        // Load libravenwood_sysprop before other libraries that may use SystemProperties.
         var libProp = RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_SYSPROP_NAME);
         System.load(libProp);
         RavenwoodRuntimeNative.reloadNativeLibrary(libProp);
@@ -165,6 +207,17 @@
         RavenwoodSystemProperties.initialize(RAVENWOOD_BUILD_PROP);
         setSystemProperties(null);
 
+        // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
+        // before loadFrameworkNativeCode() (which uses $ANDROID_LOG_TAGS).
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
+            try {
+                Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
+            } catch (ErrnoException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
         // Make sure libandroid_runtime is loaded.
         RavenwoodNativeLoader.loadFrameworkNativeCode();
 
@@ -175,15 +228,6 @@
         Objects.requireNonNull(Build.TYPE);
         Objects.requireNonNull(Build.VERSION.SDK);
 
-        if (RAVENWOOD_VERBOSE_LOGGING) {
-            RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
-            try {
-                Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
-            } catch (ErrnoException e) {
-                // Shouldn't happen.
-            }
-        }
-
         System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
         // This will let AndroidJUnit4 use the original runner.
         System.setProperty("android.junit.runner",
@@ -197,7 +241,7 @@
      */
     public static void init(RavenwoodAwareTestRunner runner) {
         if (RAVENWOOD_VERBOSE_LOGGING) {
-            Log.i(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
+            Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
         }
         if (sRunner == runner) {
             return;
@@ -221,7 +265,9 @@
             Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
         }
 
-        android.os.Process.init$ravenwood(config.mUid, config.mPid);
+        RavenwoodRuntimeState.sUid = config.mUid;
+        RavenwoodRuntimeState.sPid = config.mPid;
+        RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel;
         sOriginalIdentityToken = Binder.clearCallingIdentity();
         reinit();
         setSystemProperties(config.mSystemProperties);
@@ -308,7 +354,7 @@
      */
     public static void reset() {
         if (RAVENWOOD_VERBOSE_LOGGING) {
-            Log.i(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
+            Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
         }
         if (sRunner == null) {
             throw new RavenwoodRuntimeException("Internal error: reset() already called");
@@ -348,8 +394,8 @@
         if (sOriginalIdentityToken != -1) {
             Binder.restoreCallingIdentity(sOriginalIdentityToken);
         }
-        android.os.Process.reset$ravenwood();
-
+        RavenwoodRuntimeState.reset();
+        Process_ravenwood.reset();
         DeviceConfig_host.reset();
 
         try {
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 016de8e..7870585 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -18,6 +18,9 @@
 import android.util.Log;
 
 import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
 
 import java.io.File;
 import java.io.IOException;
@@ -27,7 +30,7 @@
 import java.nio.file.Paths;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
@@ -39,7 +42,7 @@
  */
 public class RavenwoodTestStats {
     private static final String TAG = "RavenwoodTestStats";
-    private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped";
+    private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped";
 
     private static RavenwoodTestStats sInstance;
 
@@ -66,7 +69,7 @@
     private final PrintWriter mOutputWriter;
     private final String mTestModuleName;
 
-    public final Map<Description, Map<Description, Result>> mStats = new HashMap<>();
+    public final Map<String, Map<String, Result>> mStats = new LinkedHashMap<>();
 
     /** Ctor */
     public RavenwoodTestStats() {
@@ -115,75 +118,129 @@
         return cwd.getName();
     }
 
-    private void addResult(Description classDescription, Description methodDescription,
+    private void addResult(String className, String methodName,
             Result result) {
-        mStats.compute(classDescription, (classDesc, value) -> {
+        mStats.compute(className, (className_, value) -> {
             if (value == null) {
-                value = new HashMap<>();
+                value = new LinkedHashMap<>();
             }
-            value.put(methodDescription, result);
+            // If the result is already set, don't overwrite it.
+            if (!value.containsKey(methodName)) {
+                value.put(methodName, result);
+            }
             return value;
         });
     }
 
     /**
-     * Call it when a test class is skipped.
-     */
-    public void onClassSkipped(Description classDescription) {
-        addResult(classDescription, Description.EMPTY, Result.Skipped);
-        onClassFinished(classDescription);
-    }
-
-    /**
      * Call it when a test method is finished.
      */
-    public void onTestFinished(Description classDescription, Description testDescription,
-            Result result) {
-        addResult(classDescription, testDescription, result);
+    private void onTestFinished(String className, String testName, Result result) {
+        addResult(className, testName, result);
     }
 
     /**
-     * Call it when a test class is finished.
+     * Dump all the results and clear it.
      */
-    public void onClassFinished(Description classDescription) {
-        int passed = 0;
-        int skipped = 0;
-        int failed = 0;
-        var stats = mStats.get(classDescription);
-        if (stats == null) {
-            return;
-        }
-        for (var e : stats.values()) {
-            switch (e) {
-                case Passed: passed++; break;
-                case Skipped: skipped++; break;
-                case Failed: failed++; break;
+    private void dumpAllAndClear() {
+        for (var entry : mStats.entrySet()) {
+            int passed = 0;
+            int skipped = 0;
+            int failed = 0;
+            var className = entry.getKey();
+
+            for (var e : entry.getValue().values()) {
+                switch (e) {
+                    case Passed:
+                        passed++;
+                        break;
+                    case Skipped:
+                        skipped++;
+                        break;
+                    case Failed:
+                        failed++;
+                        break;
+                }
             }
+
+            mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
+                    mTestModuleName, className, getOuterClassName(className),
+                    passed, failed, skipped);
         }
-
-        var testClass = extractTestClass(classDescription);
-
-        mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
-                mTestModuleName, (testClass == null ? "?" : testClass.getCanonicalName()),
-                classDescription, passed, failed, skipped);
         mOutputWriter.flush();
+        mStats.clear();
     }
 
-    /**
-     * Try to extract the class from a description, which is needed because
-     * ParameterizedAndroidJunit4's description doesn't contain a class.
-     */
-    private Class<?> extractTestClass(Description desc) {
-        if (desc.getTestClass() != null) {
-            return desc.getTestClass();
+    private static String getOuterClassName(String className) {
+        // Just delete the '$', because I'm not sure if the className we get here is actaully a
+        // valid class name that does exist. (it might have a parameter name, etc?)
+        int p = className.indexOf('$');
+        if (p < 0) {
+            return className;
         }
-        // Look into the children.
-        for (var child : desc.getChildren()) {
-            var fromChild = extractTestClass(child);
-            if (fromChild != null) {
-                return fromChild;
-            }
-        }
-        return null;
+        return className.substring(0, p);
     }
+
+    public void attachToRunNotifier(RunNotifier notifier) {
+        notifier.addListener(mRunListener);
+    }
+
+    private final RunListener mRunListener = new RunListener() {
+        @Override
+        public void testSuiteStarted(Description description) {
+            Log.d(TAG, "testSuiteStarted: " + description);
+        }
+
+        @Override
+        public void testSuiteFinished(Description description) {
+            Log.d(TAG, "testSuiteFinished: " + description);
+        }
+
+        @Override
+        public void testRunStarted(Description description) {
+            Log.d(TAG, "testRunStarted: " + description);
+        }
+
+        @Override
+        public void testRunFinished(org.junit.runner.Result result) {
+            Log.d(TAG, "testRunFinished: " + result);
+
+            dumpAllAndClear();
+        }
+
+        @Override
+        public void testStarted(Description description) {
+            Log.d(TAG, "  testStarted: " + description);
+        }
+
+        @Override
+        public void testFinished(Description description) {
+            Log.d(TAG, "  testFinished: " + description);
+
+            // Send "Passed", but if there's already another result sent for this, this won't
+            // override it.
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Passed);
+        }
+
+        @Override
+        public void testFailure(Failure failure) {
+            Log.d(TAG, "    testFailure: " + failure);
+
+            var description = failure.getDescription();
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Failed);
+        }
+
+        @Override
+        public void testAssumptionFailure(Failure failure) {
+            Log.d(TAG, "    testAssumptionFailure: " + failure);
+            var description = failure.getDescription();
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+        }
+
+        @Override
+        public void testIgnored(Description description) {
+            Log.d(TAG, "    testIgnored: " + description);
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+        }
+    };
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 37b0abc..d8f2b70 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.os.Build;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -67,7 +68,7 @@
     String mTargetPackageName;
 
     int mMinSdkLevel;
-    int mTargetSdkLevel;
+    int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
 
     boolean mProvideMainThread = false;
 
diff --git a/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
new file mode 100644
index 0000000..3c6a4d7
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.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.os;
+
+import android.util.Pair;
+
+public class Process_ravenwood {
+
+    private static volatile ThreadLocal<Pair<Integer, Boolean>> sThreadPriority;
+
+    static {
+        reset();
+    }
+
+    public static void reset() {
+        // Reset the thread local variable
+        sThreadPriority = ThreadLocal.withInitial(
+                () -> Pair.create(Process.THREAD_PRIORITY_DEFAULT, true));
+    }
+
+    /**
+     * Called by {@link Process#setThreadPriority(int, int)}
+     */
+    public static void setThreadPriority(int tid, int priority) {
+        if (Process.myTid() == tid) {
+            boolean backgroundOk = sThreadPriority.get().second;
+            if (priority >= Process.THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
+                throw new IllegalArgumentException(
+                        "Priority " + priority + " blocked by setCanSelfBackground()");
+            }
+            sThreadPriority.set(Pair.create(priority, backgroundOk));
+        } else {
+            throw new UnsupportedOperationException(
+                    "Cross-thread priority management not yet available in Ravenwood");
+        }
+    }
+
+    /**
+     * Called by {@link Process#setCanSelfBackground(boolean)}
+     */
+    public static void setCanSelfBackground(boolean backgroundOk) {
+        int priority = sThreadPriority.get().first;
+        sThreadPriority.set(Pair.create(priority, backgroundOk));
+    }
+
+    /**
+     * Called by {@link Process#getThreadPriority(int)}
+     */
+    public static int getThreadPriority(int tid) {
+        if (Process.myTid() == tid) {
+            return sThreadPriority.get().first;
+        } else {
+            throw new UnsupportedOperationException(
+                    "Cross-thread priority management not yet available in Ravenwood");
+        }
+    }
+}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
index e12ff24..b65668b 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
@@ -23,14 +23,6 @@
     }
 
     /**
-     * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
-     */
-    public static void ensureRavenwoodInitialized() {
-        // Initialization is now done by RavenwoodAwareTestRunner.
-        // Should we remove it?
-    }
-
-    /**
      * Called from {@link RavenwoodEnvironment#getRavenwoodRuntimePath()}.
      */
     public static String getRavenwoodRuntimePath(RavenwoodEnvironment env) {
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index c94ef31..0298171 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -16,6 +16,7 @@
 package android.system;
 
 import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
 import com.android.ravenwood.common.JvmWorkaround;
 
 import java.io.FileDescriptor;
@@ -97,4 +98,16 @@
     public static void setenv(String name, String value, boolean overwrite) throws ErrnoException {
         RavenwoodRuntimeNative.setenv(name, value, overwrite);
     }
+
+    public static int getpid() {
+        return RavenwoodRuntimeState.sPid;
+    }
+
+    public static int getuid() {
+        return RavenwoodRuntimeState.sUid;
+    }
+
+    public static int gettid() {
+        return RavenwoodRuntimeNative.gettid();
+    }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index f13189f..7b940b4 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -58,6 +58,8 @@
 
     public static native void clearSystemProperties();
 
+    public static native int gettid();
+
     public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
         return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
     }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
new file mode 100644
index 0000000..175e020
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwood;
+
+public class RavenwoodRuntimeState {
+    // This must match VMRuntime.SDK_VERSION_CUR_DEVELOPMENT.
+    public static final int CUR_DEVELOPMENT = 10000;
+
+    public static volatile int sUid;
+    public static volatile int sPid;
+    public static volatile int sTargetSdkLevel;
+
+    static {
+        reset();
+    }
+
+    public static void reset() {
+        sUid = -1;
+        sPid = -1;
+        sTargetSdkLevel = CUR_DEVELOPMENT;
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
index ba89f71..eaadac6 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
@@ -19,6 +19,7 @@
 // The original is here:
 // $ANDROID_BUILD_TOP/libcore/libart/src/main/java/dalvik/system/VMRuntime.java
 
+import com.android.ravenwood.RavenwoodRuntimeState;
 import com.android.ravenwood.common.JvmWorkaround;
 
 import java.lang.reflect.Array;
@@ -52,4 +53,8 @@
     public long addressOf(Object obj) {
         return JvmWorkaround.getInstance().addressOf(obj);
     }
+
+    public int getTargetSdkVersion() {
+        return RavenwoodRuntimeState.sTargetSdkLevel;
+    }
 }
diff --git a/ravenwood/runtime-jni/ravenwood_initializer.cpp b/ravenwood/runtime-jni/ravenwood_initializer.cpp
new file mode 100644
index 0000000..89fb7c3
--- /dev/null
+++ b/ravenwood/runtime-jni/ravenwood_initializer.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ /*
+  * This file is compiled into a single SO file, which we load at the very first.
+  * We can do process-wide initialization here.
+  */
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "jni_helper.h"
+
+static void maybeRedirectLog() {
+    auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
+    if (ravenwoodLogOut == NULL) {
+        return;
+    }
+    ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut);
+
+    // Redirect stdin / stdout to /dev/tty.
+    int ttyFd = open(ravenwoodLogOut, O_WRONLY | O_APPEND);
+    if (ttyFd == -1) {
+        ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut,
+                strerror(errno));
+        return;
+    }
+    dup2(ttyFd, 1);
+    dup2(ttyFd, 2);
+}
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+    ALOGI("%s: JNI_OnLoad", __FILE__);
+
+    maybeRedirectLog();
+    return JNI_VERSION_1_4;
+}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 3ff0848..c1993f6 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -17,6 +17,7 @@
 #include <fcntl.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <unistd.h>
 #include <utils/misc.h>
 
@@ -173,6 +174,12 @@
     throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
 }
 
+
+static jint Linux_gettid(JNIEnv* env, jobject) {
+    // gettid(2() was added in glibc 2.30 but Android uses an older version in prebuilt.
+    return syscall(__NR_gettid);
+}
+
 // ---- Registration ----
 
 extern void register_android_system_OsConstants(JNIEnv* env);
@@ -189,6 +196,7 @@
     { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
     { "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open },
     { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
+    { "gettid", "()I", (void*)Linux_gettid },
 };
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
index 5d623e0..1910100 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -18,12 +18,38 @@
 # Options:
 #
 #   -s: "Smoke" test -- skip slow tests (SysUI, ICU)
+#
+#   -x PCRE: Specify exclusion filter in PCRE
+#            Example: -x '^(Cts|hoststub)' # Exclude CTS and hoststubgen tests.
+#
+#   -f PCRE: Specify inclusion filter in PCRE
+
+
+# Regex to identify slow tests, in PCRE
+SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood|CarSystemUIRavenTests)$'
 
 smoke=0
-while getopts "s" opt; do
+include_re=""
+exclude_re=""
+smoke_exclude_re=""
+dry_run=""
+while getopts "sx:f:d" opt; do
 case "$opt" in
     s)
-        smoke=1
+        # Remove slow tests.
+        smoke_exclude_re="$SLOW_TEST_RE"
+        ;;
+    x)
+        # Take a PCRE from the arg, and use it as an exclusion filter.
+        exclude_re="$OPTARG"
+        ;;
+    f)
+        # Take a PCRE from the arg, and use it as an inclusion filter.
+        include_re="$OPTARG"
+        ;;
+    d)
+        # Dry run
+        dry_run="echo"
         ;;
     '?')
         exit 1
@@ -35,21 +61,46 @@
 all_tests=(hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test ravenwood-stats-checker)
 all_tests+=( $(${0%/*}/list-ravenwood-tests.sh) )
 
-# Regex to identify slow tests, in PCRE
-slow_tests_re='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$'
-
-if (( $smoke )) ; then
-    # Remove the slow tests.
-    all_tests=( $(
-        for t in "${all_tests[@]}"; do
-            echo $t | grep -vP "$slow_tests_re"
-        done
-    ) )
-fi
-
-run() {
-    echo "Running: $*"
-    "${@}"
+filter() {
+    local re="$1"
+    local grep_arg="$2"
+    if [[ "$re" == "" ]] ; then
+        cat # No filtering
+    else
+        grep $grep_arg -iP "$re"
+    fi
 }
 
-run ${ATEST:-atest} "${all_tests[@]}"
+filter_in() {
+    filter "$1"
+}
+
+filter_out() {
+    filter "$1" -v
+}
+
+
+# Remove the slow tests.
+targets=( $(
+    for t in "${all_tests[@]}"; do
+        echo $t | filter_in "$include_re" | filter_out "$smoke_exclude_re" | filter_out "$exclude_re"
+    done
+) )
+
+# Show the target tests
+
+echo "Target tests:"
+for t in "${targets[@]}"; do
+    echo "  $t"
+done
+
+# Calculate the removed tests.
+
+diff="$(diff  <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') )"
+
+if [[ "$diff" != "" ]]; then
+    echo "Excluded tests:"
+    echo "$diff"
+fi
+
+$dry_run ${ATEST:-atest} "${targets[@]}"
diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
index ce0033d..4895a1a 100644
--- a/ravenwood/tests/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -43,9 +43,12 @@
 
         // To make sure it won't cause VerifyError (b/324063814)
         "platformprotosnano",
+
+        "com.android.internal.os.flags-aconfig-java",
     ],
     srcs: [
         "test/**/*.java",
+        "test/**/*.kt",
     ],
     jni_libs: [
         "libravenwoodbivalenttest_jni",
@@ -58,10 +61,12 @@
     // TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
     exclude_srcs: [
         "test/**/ravenizer/*.java",
+        "test/**/ravenizer/*.kt",
     ],
     static_libs: [
         "junit",
         "truth",
+        "flag-junit",
         "ravenwood-junit",
     ],
     test_suites: [
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
new file mode 100644
index 0000000..fd6d6fb
--- /dev/null
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.bivalenttest.aconfig
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.os.Flags
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigSimpleReadTests {
+    @Test
+    fun testFalseFlags() {
+        assertFalse(Flags.ravenwoodFlagRo1())
+        assertFalse(Flags.ravenwoodFlagRw1())
+    }
+
+    @Test
+    @Ignore // TODO: Enable this test after rolling out the "2" flags.
+    fun testTrueFlags() {
+        assertTrue(Flags.ravenwoodFlagRo2())
+        assertTrue(Flags.ravenwoodFlagRw2())
+    }
+}
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigCheckFlagsRuleTests {
+    @Rule
+    @JvmField
+    val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_RAVENWOOD_FLAG_RO_1)
+    fun testRequireFlagsEnabledRo() {
+        fail("This test shouldn't be executed")
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_RAVENWOOD_FLAG_RW_1)
+    fun testRequireFlagsEnabledRw() {
+        fail("This test shouldn't be executed")
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RO_2)
+    @Ignore // TODO: Enable this test after rolling out the "2" flags.
+    fun testRequireFlagsDisabledRo() {
+        fail("This test shouldn't be executed")
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RW_2)
+    @Ignore // TODO: Enable this test after rolling out the "2" flags.
+    fun testRequireFlagsDisabledRw() {
+        fail("This test shouldn't be executed")
+    }
+}
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigSetFlagsRuleWithDefaultTests {
+    @Rule
+    @JvmField
+    val setFlagsRule = SetFlagsRule()
+
+    @Test
+    @EnableFlags(Flags.FLAG_RAVENWOOD_FLAG_RO_1)
+    fun testSetRoFlag() {
+        assertTrue(Flags.ravenwoodFlagRo1())
+        assertFalse(Flags.ravenwoodFlagRw1())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RAVENWOOD_FLAG_RW_1)
+    fun testSetRwFlag() {
+        assertFalse(Flags.ravenwoodFlagRo1())
+        assertTrue(Flags.ravenwoodFlagRw1())
+    }
+}
diff --git a/ravenwood/tests/runtime-test/Android.bp b/ravenwood/tests/runtime-test/Android.bp
index 4102920..0c0df1f 100644
--- a/ravenwood/tests/runtime-test/Android.bp
+++ b/ravenwood/tests/runtime-test/Android.bp
@@ -10,6 +10,9 @@
 android_ravenwood_test {
     name: "RavenwoodRuntimeTest",
 
+    libs: [
+        "ravenwood-helper-runtime",
+    ],
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.ext.junit",
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
new file mode 100644
index 0000000..8e04b69
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.runtimetest;
+
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.system.Os;
+
+import com.android.ravenwood.RavenwoodRuntimeState;
+
+import dalvik.system.VMRuntime;
+
+import org.junit.Test;
+
+public class IdentityTest {
+
+    @RavenwoodConfig.Config
+    public static final RavenwoodConfig sConfig =
+            new RavenwoodConfig.Builder()
+                    .setTargetSdkLevel(UPSIDE_DOWN_CAKE)
+                    .setProcessApp()
+                    .build();
+
+    @Test
+    public void testUid() {
+        assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid);
+        assertEquals(FIRST_APPLICATION_UID, Os.getuid());
+        assertEquals(FIRST_APPLICATION_UID, Process.myUid());
+        assertEquals(FIRST_APPLICATION_UID, Binder.getCallingUid());
+    }
+
+    @Test
+    public void testPid() {
+        int pid = RavenwoodRuntimeState.sPid;
+        assertEquals(pid, Os.getpid());
+        assertEquals(pid, Process.myPid());
+        assertEquals(pid, Binder.getCallingPid());
+    }
+
+    @Test
+    public void testTargetSdkLevel() {
+        assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT);
+        assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel);
+        assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion());
+    }
+}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
index c2230c7..c55506a 100644
--- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
@@ -24,6 +24,8 @@
 import static android.system.OsConstants.S_ISSOCK;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
 
@@ -51,10 +53,12 @@
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class OsTest {
+
     public interface ConsumerWithThrow<T> {
         void accept(T var1) throws Exception;
     }
@@ -165,6 +169,35 @@
         });
     }
 
+    private static class TestThread extends Thread {
+
+        final CountDownLatch mLatch = new CountDownLatch(1);
+        int mTid;
+
+        TestThread() {
+            setDaemon(true);
+        }
+
+        @Override
+        public void run() {
+            mTid = Os.gettid();
+            mLatch.countDown();
+        }
+    }
+
+    @Test
+    public void testGetTid() throws InterruptedException {
+        var t1 = new TestThread();
+        var t2 = new TestThread();
+        t1.start();
+        t2.start();
+        // Wait for thread execution
+        assertTrue(t1.mLatch.await(1, TimeUnit.SECONDS));
+        assertTrue(t2.mLatch.await(1, TimeUnit.SECONDS));
+        // Make sure the tid is unique per-thread
+        assertNotEquals(t1.mTid, t2.mTid);
+    }
+
     // Verify StructStat values from libcore against native JVM PosixFileAttributes
     private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) {
         assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim));
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
new file mode 100644
index 0000000..d25b5c1
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.runtimetest;
+
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Process;
+import android.system.Os;
+
+import org.junit.Test;
+
+public class ProcessTest {
+
+    @Test
+    public void testGetUidPidTid() {
+        assertEquals(Os.getuid(), Process.myUid());
+        assertEquals(Os.getpid(), Process.myPid());
+        assertEquals(Os.gettid(), Process.myTid());
+    }
+
+    @Test
+    public void testThreadPriority() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> Process.getThreadPriority(Process.myTid() + 1));
+        assertThrows(UnsupportedOperationException.class,
+                () -> Process.setThreadPriority(Process.myTid() + 1, THREAD_PRIORITY_DEFAULT));
+        assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+        Process.setThreadPriority(THREAD_PRIORITY_FOREGROUND);
+        assertEquals(THREAD_PRIORITY_FOREGROUND, Process.getThreadPriority(Process.myTid()));
+        Process.setCanSelfBackground(false);
+        Process.setThreadPriority(THREAD_PRIORITY_DEFAULT);
+        assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+        assertThrows(IllegalArgumentException.class,
+                () -> Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND));
+    }
+}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index a02082d..f47aaba 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -327,6 +327,10 @@
     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
 }
 
+fun ClassNode.isAbstract(): Boolean {
+    return (this.access and Opcodes.ACC_ABSTRACT) != 0
+}
+
 fun MethodNode.isSynthetic(): Boolean {
     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
 }
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index 32dcbe5..a0e5599 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -46,7 +46,7 @@
     var enableValidation: SetOnce<Boolean> = SetOnce(true),
 
     /** Whether the validation failure is fatal or not. */
-    var fatalValidation: SetOnce<Boolean> = SetOnce(false),
+    var fatalValidation: SetOnce<Boolean> = SetOnce(true),
 
     /** Whether to remove mockito and dexmaker classes. */
     var stripMockito: SetOnce<Boolean> = SetOnce(false),
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
index 27092d2..8ec0932 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
@@ -16,10 +16,12 @@
 package com.android.platform.test.ravenwood.ravenizer
 
 import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.isAbstract
 import com.android.hoststubgen.asm.startsWithAny
 import com.android.hoststubgen.asm.toHumanReadableClassName
 import com.android.hoststubgen.log
 import org.objectweb.asm.tree.ClassNode
+import java.util.regex.Pattern
 
 fun validateClasses(classes: ClassNodes): Boolean {
     var allOk = true
@@ -41,25 +43,35 @@
     }
     var allOk = true
 
+    log.i("Checking ${cn.name.toHumanReadableClassName()}")
+
     // See if there's any class that extends a legacy base class.
     // But ignore the base classes in android.test.
-    if (!cn.name.startsWithAny("android/test/")) {
-        allOk = checkSuperClass(cn, cn, classes) && allOk
+    if (!cn.isAbstract() && !cn.name.startsWith("android/test/")
+        && !isAllowListedLegacyTest(cn)
+        ) {
+        allOk = checkSuperClassForJunit3(cn, cn, classes) && allOk
     }
     return allOk
 }
 
-fun checkSuperClass(targetClass: ClassNode, currentClass: ClassNode, classes: ClassNodes): Boolean {
+fun checkSuperClassForJunit3(
+    targetClass: ClassNode,
+    currentClass: ClassNode,
+    classes: ClassNodes,
+): Boolean {
     if (currentClass.superName == null || currentClass.superName == "java/lang/Object") {
         return true // No parent class
     }
+    // Make sure the class doesn't extend a junit3 TestCase class.
     if (currentClass.superName.isLegacyTestBaseClass()) {
         log.e("Error: Class ${targetClass.name.toHumanReadableClassName()} extends"
-                + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}.")
+                + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}"
+                + ", which is not supported on Ravenwood. Please migrate to Junit4 syntax.")
         return false
     }
     classes.findClass(currentClass.superName)?.let {
-        return checkSuperClass(targetClass, it, classes)
+        return checkSuperClassForJunit3(targetClass, it, classes)
     }
     // Super class not found.
     // log.w("Class ${currentClass.superName} not found.")
@@ -73,9 +85,64 @@
     return this.startsWithAny(
         "junit/framework/TestCase",
 
-        // In case the test doesn't statically include JUnit, we need
+        // In case the test doesn't statically include JUnit, we need the following.
         "android/test/AndroidTestCase",
         "android/test/InstrumentationTestCase",
         "android/test/InstrumentationTestSuite",
     )
 }
+
+private val allowListedLegacyTests = setOf(
+// List of existing test classes that use the JUnit3 syntax. We exempt them for now, but
+// will reject any more of them.
+//
+// Note, we want internal class names, but for convenience, we use '.'s and '%'s here
+// and replace them later. (a '$' would be parsed as a string template.)
+    *"""
+android.util.proto.cts.DebuggingTest
+android.util.proto.cts.EncodedBufferTest
+android.util.proto.cts.ProtoOutputStreamBoolTest
+android.util.proto.cts.ProtoOutputStreamBytesTest
+android.util.proto.cts.ProtoOutputStreamDoubleTest
+android.util.proto.cts.ProtoOutputStreamEnumTest
+android.util.proto.cts.ProtoOutputStreamFixed32Test
+android.util.proto.cts.ProtoOutputStreamFixed64Test
+android.util.proto.cts.ProtoOutputStreamFloatTest
+android.util.proto.cts.ProtoOutputStreamInt32Test
+android.util.proto.cts.ProtoOutputStreamInt64Test
+android.util.proto.cts.ProtoOutputStreamObjectTest
+android.util.proto.cts.ProtoOutputStreamSFixed32Test
+android.util.proto.cts.ProtoOutputStreamSFixed64Test
+android.util.proto.cts.ProtoOutputStreamSInt32Test
+android.util.proto.cts.ProtoOutputStreamSInt64Test
+android.util.proto.cts.ProtoOutputStreamStringTest
+android.util.proto.cts.ProtoOutputStreamSwitchedWriteTest
+android.util.proto.cts.ProtoOutputStreamTagTest
+android.util.proto.cts.ProtoOutputStreamUInt32Test
+android.util.proto.cts.ProtoOutputStreamUInt64Test
+
+android.os.cts.BadParcelableExceptionTest
+android.os.cts.DeadObjectExceptionTest
+android.os.cts.ParcelFormatExceptionTest
+android.os.cts.PatternMatcherTest
+android.os.cts.RemoteExceptionTest
+
+android.os.storage.StorageManagerBaseTest
+android.os.storage.StorageManagerIntegrationTest
+android.util.LogTest%PerformanceTest
+
+com.android.server.power.stats.BatteryStatsCounterTest
+com.android.server.power.stats.BatteryStatsDualTimerTest
+com.android.server.power.stats.BatteryStatsDurationTimerTest
+com.android.server.power.stats.BatteryStatsSamplingTimerTest
+com.android.server.power.stats.BatteryStatsStopwatchTimerTest
+com.android.server.power.stats.BatteryStatsTimeBaseTest
+com.android.server.power.stats.BatteryStatsTimerTest
+
+    """.trim().replace('%', '$').replace('.', '/')
+        .split(Pattern.compile("""\s+""")).toTypedArray()
+)
+
+private fun isAllowListedLegacyTest(targetClass: ClassNode): Boolean {
+    return allowListedLegacyTests.contains(targetClass.name)
+}
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 2e9a4dc..a10039f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -462,7 +462,9 @@
 
                         @Override
                         public void onShown() {
-                            mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size());
+                            if (mCallback != null) {
+                                mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size());
+                            }
                         }
 
                         @Override
@@ -511,7 +513,9 @@
 
                         @Override
                         public void startIntentSender(IntentSender intentSender) {
-                            mCallback.startIntentSenderAndFinishSession(intentSender);
+                            if (mCallback != null) {
+                                mCallback.startIntentSenderAndFinishSession(intentSender);
+                            }
                         }
 
                         private void log(int type) {
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index 82c2038..71885fd 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -18,22 +18,30 @@
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.ISerialManager;
 import android.hardware.SerialManagerInternal;
 import android.os.ParcelFileDescriptor;
 import android.os.PermissionEnforcer;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.function.Supplier;
 
 @android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SerialService extends ISerialManager.Stub {
+    private static final String TAG = "SerialService";
+
     private final Context mContext;
 
     @GuardedBy("mSerialPorts")
@@ -50,7 +58,7 @@
             final String[] serialPorts = getSerialPorts(context);
             for (String serialPort : serialPorts) {
                 mSerialPorts.put(serialPort, () -> {
-                    return native_open(serialPort);
+                    return tryOpen(serialPort);
                 });
             }
         }
@@ -135,5 +143,14 @@
         }
     };
 
-    private native ParcelFileDescriptor native_open(String path);
+    private static @Nullable ParcelFileDescriptor tryOpen(String path) {
+        try {
+            FileDescriptor fd = Os.open(path, OsConstants.O_RDWR | OsConstants.O_NOCTTY, 0);
+            return new ParcelFileDescriptor(fd);
+        } catch (ErrnoException e) {
+            Slog.e(TAG, "Could not open: " + path, e);
+            // We return null to preserve API semantics from earlier implementation variants.
+            return null;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index dc7749c..06e6c8b 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -525,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
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d11abf8..4a4a1b4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5239,6 +5239,11 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                if (action == null) {
+                    return;
+                }
+
                 String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
                 if (pkgs != null) {
                     for (String pkg : pkgs) {
diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java
index 5db6dc7..6ccb3ee 100644
--- a/services/core/java/com/android/server/appbinding/AppBindingService.java
+++ b/services/core/java/com/android/server/appbinding/AppBindingService.java
@@ -235,6 +235,9 @@
             }
 
             final String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
 
             if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 onUserRemoved(userId);
diff --git a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
index 01f770b..9fa5da4 100644
--- a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
+++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
@@ -133,6 +133,9 @@
     private static final EventLogger sLogger = new EventLogger(
             AudioService.LOG_NB_EVENTS_LOUDNESS_CODEC, "Loudness updates");
 
+    private final Object mDispatcherLock = new Object();
+
+    @GuardedBy("mDispatcherLock")
     private final LoudnessRemoteCallbackList mLoudnessUpdateDispatchers =
             new LoudnessRemoteCallbackList(this);
 
@@ -339,12 +342,16 @@
     }
 
     void registerLoudnessCodecUpdatesDispatcher(ILoudnessCodecUpdatesDispatcher dispatcher) {
-        mLoudnessUpdateDispatchers.register(dispatcher, Binder.getCallingPid());
+        synchronized (mDispatcherLock) {
+            mLoudnessUpdateDispatchers.register(dispatcher, Binder.getCallingPid());
+        }
     }
 
     void unregisterLoudnessCodecUpdatesDispatcher(
             ILoudnessCodecUpdatesDispatcher dispatcher) {
-        mLoudnessUpdateDispatchers.unregister(dispatcher);
+        synchronized (mDispatcherLock) {
+            mLoudnessUpdateDispatchers.unregister(dispatcher);
+        }
     }
 
     void startLoudnessCodecUpdates(int sessionId) {
@@ -640,17 +647,20 @@
             Log.d(TAG,
                     "dispatchNewLoudnessParameters: sessionId " + sessionId + " bundle: " + bundle);
         }
-        final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast();
-        for (int i = 0; i < nbDispatchers; ++i) {
-            try {
-                mLoudnessUpdateDispatchers.getBroadcastItem(i)
-                        .dispatchLoudnessCodecParameterChange(sessionId, bundle);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error dispatching for sessionId " + sessionId + " bundle: " + bundle,
-                        e);
+        synchronized (mDispatcherLock) {
+            final int nbDispatchers = mLoudnessUpdateDispatchers.beginBroadcast();
+            for (int i = 0; i < nbDispatchers; ++i) {
+                try {
+                    mLoudnessUpdateDispatchers.getBroadcastItem(i)
+                            .dispatchLoudnessCodecParameterChange(sessionId, bundle);
+                } catch (RemoteException e) {
+                    Log.e(TAG,
+                            "Error dispatching for sessionId " + sessionId + " bundle: " + bundle,
+                            e);
+                }
             }
+            mLoudnessUpdateDispatchers.finishBroadcast();
         }
-        mLoudnessUpdateDispatchers.finishBroadcast();
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 97c32b9..4993412 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -255,17 +255,6 @@
         }
     }
 
-    private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
-        // Different languages are never compatible
-        if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
-            return false;
-        }
-        // If both the system and the keyboard layout have a country specifier, they must be equal.
-        return TextUtils.isEmpty(systemLocale.getCountry())
-                || TextUtils.isEmpty(keyboardLocale.getCountry())
-                || systemLocale.getCountry().equals(keyboardLocale.getCountry());
-    }
-
     @MainThread
     private void updateKeyboardLayouts() {
         // Scan all input devices state for keyboard layouts that have been uninstalled.
@@ -953,21 +942,33 @@
             return;
         }
 
+        List<String> layoutNames = new ArrayList<>();
+        for (String layoutDesc : config.getConfiguredLayouts()) {
+            KeyboardLayout kl = getKeyboardLayout(layoutDesc);
+            if (kl == null) {
+                // b/349033234: Weird state with stale keyboard layout configured.
+                // Possibly due to race condition between KCM providing package being removed and
+                // corresponding layouts being removed from Datastore and cache.
+                // {@see updateKeyboardLayouts()}
+                //
+                // Ideally notification will be correctly shown after the keyboard layouts are
+                // configured again with the new package state.
+                return;
+            }
+            layoutNames.add(kl.getLabel());
+        }
         showKeyboardLayoutNotification(
                 r.getString(
                         R.string.keyboard_layout_notification_selected_title,
                         inputDevice.getName()),
-                createConfiguredNotificationText(mContext, config.getConfiguredLayouts()),
+                createConfiguredNotificationText(mContext, layoutNames),
                 inputDevice);
     }
 
     @MainThread
     private String createConfiguredNotificationText(@NonNull Context context,
-            @NonNull Set<String> selectedLayouts) {
+            @NonNull List<String> layoutNames) {
         final Resources r = context.getResources();
-        List<String> layoutNames = new ArrayList<>();
-        selectedLayouts.forEach(
-                (layoutDesc) -> layoutNames.add(getKeyboardLayout(layoutDesc).getLabel()));
         Collections.sort(layoutNames);
         switch (layoutNames.size()) {
             case 1:
diff --git a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
deleted file mode 100644
index e831e40..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
+++ /dev/null
@@ -1,66 +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.Nullable;
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/** Helper class for parsing rule metadata. */
-public class RuleMetadataParser {
-
-    public static final String RULE_PROVIDER_TAG = "P";
-    public static final String VERSION_TAG = "V";
-
-    /** Parse the rule metadata from an input stream. */
-    @Nullable
-    public static RuleMetadata parse(InputStream inputStream)
-            throws XmlPullParserException, IOException {
-
-        String ruleProvider = "";
-        String version = "";
-
-        TypedXmlPullParser xmlPullParser = Xml.resolvePullParser(inputStream);
-
-        int eventType;
-        while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
-            if (eventType == XmlPullParser.START_TAG) {
-                String tag = xmlPullParser.getName();
-                switch (tag) {
-                    case RULE_PROVIDER_TAG:
-                        ruleProvider = xmlPullParser.nextText();
-                        break;
-                    case VERSION_TAG:
-                        version = xmlPullParser.nextText();
-                        break;
-                    default:
-                        throw new IllegalStateException("Unknown tag in metadata: " + tag);
-                }
-            }
-        }
-
-        return new RuleMetadata(ruleProvider, version);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
deleted file mode 100644
index 8ba5870..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ /dev/null
@@ -1,324 +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.serializer;
-
-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.INSTALLER_ALLOWED_BY_MANIFEST_START;
-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.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.InstallerAllowedByManifestFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.integrity.model.BitOutputStream;
-import com.android.server.integrity.model.ByteTrackedOutputStream;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
-public class RuleBinarySerializer implements RuleSerializer {
-    static final int TOTAL_RULE_SIZE_LIMIT = 200000;
-    static final int INDEXED_RULE_SIZE_LIMIT = 100000;
-    static final int NONINDEXED_RULE_SIZE_LIMIT = 1000;
-
-    // Get the byte representation for a list of rules.
-    @Override
-    public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
-            throws RuleSerializeException {
-        try {
-            ByteArrayOutputStream rulesOutputStream = new ByteArrayOutputStream();
-            serialize(rules, formatVersion, rulesOutputStream, new ByteArrayOutputStream());
-            return rulesOutputStream.toByteArray();
-        } catch (Exception e) {
-            throw new RuleSerializeException(e.getMessage(), e);
-        }
-    }
-
-    // Get the byte representation for a list of rules, and write them to an output stream.
-    @Override
-    public void serialize(
-            List<Rule> rules,
-            Optional<Integer> formatVersion,
-            OutputStream rulesFileOutputStream,
-            OutputStream indexingFileOutputStream)
-            throws RuleSerializeException {
-        try {
-            if (rules == null) {
-                throw new IllegalArgumentException("Null rules cannot be serialized.");
-            }
-
-            if (rules.size() > TOTAL_RULE_SIZE_LIMIT) {
-                throw new IllegalArgumentException("Too many rules provided: " + rules.size());
-            }
-
-            // Determine the indexing groups and the order of the rules within each indexed group.
-            Map<Integer, Map<String, List<Rule>>> indexedRules =
-                    RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
-
-            // Validate the rule blocks are not larger than expected limits.
-            verifySize(indexedRules.get(PACKAGE_NAME_INDEXED), INDEXED_RULE_SIZE_LIMIT);
-            verifySize(indexedRules.get(APP_CERTIFICATE_INDEXED), INDEXED_RULE_SIZE_LIMIT);
-            verifySize(indexedRules.get(NOT_INDEXED), NONINDEXED_RULE_SIZE_LIMIT);
-
-            // Serialize the rules.
-            ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
-                    new ByteTrackedOutputStream(rulesFileOutputStream);
-            serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
-            LinkedHashMap<String, Integer> packageNameIndexes =
-                    serializeRuleList(
-                            indexedRules.get(PACKAGE_NAME_INDEXED),
-                            ruleFileByteTrackedOutputStream);
-            LinkedHashMap<String, Integer> appCertificateIndexes =
-                    serializeRuleList(
-                            indexedRules.get(APP_CERTIFICATE_INDEXED),
-                            ruleFileByteTrackedOutputStream);
-            LinkedHashMap<String, Integer> unindexedRulesIndexes =
-                    serializeRuleList(
-                            indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
-
-            // Serialize their indexes.
-            BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
-            serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
-            serializeIndexGroup(
-                    appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
-            serializeIndexGroup(
-                    unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
-            indexingBitOutputStream.flush();
-        } catch (Exception e) {
-            throw new RuleSerializeException(e.getMessage(), e);
-        }
-    }
-
-    private void verifySize(Map<String, List<Rule>> ruleListMap, int ruleSizeLimit) {
-        int totalRuleCount =
-                ruleListMap.values().stream()
-                        .map(list -> list.size())
-                        .collect(Collectors.summingInt(Integer::intValue));
-        if (totalRuleCount > ruleSizeLimit) {
-            throw new IllegalArgumentException(
-                    "Too many rules provided in the indexing group. Provided "
-                            + totalRuleCount
-                            + " limit "
-                            + ruleSizeLimit);
-        }
-    }
-
-    private void serializeRuleFileMetadata(
-            Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
-            throws IOException {
-        int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
-
-        BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
-        bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
-        bitOutputStream.flush();
-    }
-
-    private LinkedHashMap<String, Integer> serializeRuleList(
-            Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
-            throws IOException {
-        Preconditions.checkArgument(
-                rulesMap != null, "serializeRuleList should never be called with null rule list.");
-
-        BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
-        LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
-        indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
-        List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList());
-        int indexTracker = 0;
-        for (String key : sortedKeys) {
-            if (indexTracker >= INDEXING_BLOCK_SIZE) {
-                indexMapping.put(key, outputStream.getWrittenBytesCount());
-                indexTracker = 0;
-            }
-
-            for (Rule rule : rulesMap.get(key)) {
-                serializeRule(rule, bitOutputStream);
-                bitOutputStream.flush();
-                indexTracker++;
-            }
-        }
-        indexMapping.put(END_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
-        return indexMapping;
-    }
-
-    private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
-        if (rule == null) {
-            throw new IllegalArgumentException("Null rule can not be serialized");
-        }
-
-        // Start with a '1' bit to mark the start of a rule.
-        bitOutputStream.setNext();
-
-        serializeFormula(rule.getFormula(), bitOutputStream);
-        bitOutputStream.setNext(EFFECT_BITS, rule.getEffect());
-
-        // End with a '1' bit to mark the end of a rule.
-        bitOutputStream.setNext();
-    }
-
-    private void serializeFormula(IntegrityFormula formula, BitOutputStream bitOutputStream)
-            throws IOException {
-        if (formula instanceof AtomicFormula) {
-            serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
-        } else if (formula instanceof CompoundFormula) {
-            serializeCompoundFormula((CompoundFormula) formula, bitOutputStream);
-        } else if (formula instanceof InstallerAllowedByManifestFormula) {
-            bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START);
-        } else {
-            throw new IllegalArgumentException(
-                    String.format("Invalid formula type: %s", formula.getClass()));
-        }
-    }
-
-    private void serializeCompoundFormula(
-            CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
-        if (compoundFormula == null) {
-            throw new IllegalArgumentException("Null compound formula can not be serialized");
-        }
-
-        bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_START);
-        bitOutputStream.setNext(CONNECTOR_BITS, compoundFormula.getConnector());
-        for (IntegrityFormula formula : compoundFormula.getFormulas()) {
-            serializeFormula(formula, bitOutputStream);
-        }
-        bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_END);
-    }
-
-    private void serializeAtomicFormula(
-            AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
-        if (atomicFormula == null) {
-            throw new IllegalArgumentException("Null atomic formula can not be serialized");
-        }
-
-        bitOutputStream.setNext(SEPARATOR_BITS, ATOMIC_FORMULA_START);
-        bitOutputStream.setNext(KEY_BITS, atomicFormula.getKey());
-        if (atomicFormula.getTag() == AtomicFormula.STRING_ATOMIC_FORMULA_TAG) {
-            AtomicFormula.StringAtomicFormula stringAtomicFormula =
-                    (AtomicFormula.StringAtomicFormula) atomicFormula;
-            bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
-            serializeStringValue(
-                    stringAtomicFormula.getValue(),
-                    stringAtomicFormula.getIsHashedValue(),
-                    bitOutputStream);
-        } else if (atomicFormula.getTag() == AtomicFormula.LONG_ATOMIC_FORMULA_TAG) {
-            AtomicFormula.LongAtomicFormula longAtomicFormula =
-                    (AtomicFormula.LongAtomicFormula) atomicFormula;
-            bitOutputStream.setNext(OPERATOR_BITS, longAtomicFormula.getOperator());
-            // TODO(b/147880712): Temporary hack until we support long values in bitOutputStream
-            long value = longAtomicFormula.getValue();
-            serializeIntValue((int) (value >>> 32), bitOutputStream);
-            serializeIntValue((int) value, bitOutputStream);
-        } else if (atomicFormula.getTag() == AtomicFormula.BOOLEAN_ATOMIC_FORMULA_TAG) {
-            AtomicFormula.BooleanAtomicFormula booleanAtomicFormula =
-                    (AtomicFormula.BooleanAtomicFormula) atomicFormula;
-            bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
-            serializeBooleanValue(booleanAtomicFormula.getValue(), bitOutputStream);
-        } else {
-            throw new IllegalArgumentException(
-                    String.format("Invalid atomic formula type: %s", atomicFormula.getClass()));
-        }
-    }
-
-    private void serializeIndexGroup(
-            LinkedHashMap<String, Integer> indexes,
-            BitOutputStream bitOutputStream,
-            boolean isIndexed)
-            throws IOException {
-        // Output the starting location of this indexing group.
-        serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
-        serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
-
-        // If the group is indexed, output the locations of the indexes.
-        if (isIndexed) {
-            for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
-                if (!entry.getKey().equals(START_INDEXING_KEY)
-                        && !entry.getKey().equals(END_INDEXING_KEY)) {
-                    serializeStringValue(
-                            entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
-                    serializeIntValue(entry.getValue(), bitOutputStream);
-                }
-            }
-        }
-
-        // Output the end location of this indexing group.
-        serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
-        serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
-    }
-
-    private void serializeStringValue(
-            String value, boolean isHashedValue, BitOutputStream bitOutputStream)
-            throws IOException {
-        if (value == null) {
-            throw new IllegalArgumentException("String value can not be null.");
-        }
-        byte[] valueBytes = getBytesForString(value, isHashedValue);
-
-        bitOutputStream.setNext(isHashedValue);
-        bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length);
-        for (byte valueByte : valueBytes) {
-            bitOutputStream.setNext(/* numOfBits= */ 8, valueByte);
-        }
-    }
-
-    private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
-        bitOutputStream.setNext(/* numOfBits= */ 32, value);
-    }
-
-    private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
-            throws IOException {
-        bitOutputStream.setNext(value);
-    }
-
-    // Get the byte array for a value.
-    // If the value is not hashed, use its byte array form directly.
-    // If the value is hashed, get the raw form decoding of the value. All hashed values are
-    // hex-encoded. Serialized values are in raw form.
-    private static byte[] getBytesForString(String value, boolean isHashedValue) {
-        if (!isHashedValue) {
-            return value.getBytes(StandardCharsets.UTF_8);
-        }
-        return IntegrityUtils.getBytesFromHexDigest(value);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
deleted file mode 100644
index 2cbd4ede..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
+++ /dev/null
@@ -1,69 +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.serializer;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** Holds the indexing type and indexing key of a given formula. */
-class RuleIndexingDetails {
-
-    static final int NOT_INDEXED = 0;
-    static final int PACKAGE_NAME_INDEXED = 1;
-    static final int APP_CERTIFICATE_INDEXED = 2;
-
-    static final String DEFAULT_RULE_KEY = "N/A";
-
-    /** Represents which indexed file the rule should be located. */
-    @IntDef(
-            value = {
-                    NOT_INDEXED,
-                    PACKAGE_NAME_INDEXED,
-                    APP_CERTIFICATE_INDEXED
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface IndexType {
-    }
-
-    private @IndexType int mIndexType;
-    private String mRuleKey;
-
-    /** Constructor without a ruleKey for {@code NOT_INDEXED}. */
-    RuleIndexingDetails(@IndexType int indexType) {
-        this.mIndexType = indexType;
-        this.mRuleKey = DEFAULT_RULE_KEY;
-    }
-
-    /** Constructor with a ruleKey for indexed rules. */
-    RuleIndexingDetails(@IndexType int indexType, String ruleKey) {
-        this.mIndexType = indexType;
-        this.mRuleKey = ruleKey;
-    }
-
-    /** Returns the indexing type for the rule. */
-    @IndexType
-    public int getIndexType() {
-        return mIndexType;
-    }
-
-    /** Returns the identified rule key. */
-    public String getRuleKey() {
-        return mRuleKey;
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
deleted file mode 100644
index e723559..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ /dev/null
@@ -1,151 +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.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-/** A helper class for identifying the indexing type and key of a given rule. */
-class RuleIndexingDetailsIdentifier {
-
-    /**
-     * Splits a given rule list into three indexing categories. Each rule category is returned as a
-     * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for
-     * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for
-     * NOT_INDEXED rules.
-     */
-    public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets(
-            List<Rule> rules) {
-        if (rules == null) {
-            throw new IllegalArgumentException(
-                    "Index buckets cannot be created for null rule list.");
-        }
-
-        Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
-        typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap());
-        typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>());
-        typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>());
-
-        // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
-        // entries sorted by their index key.
-        for (Rule rule : rules) {
-            RuleIndexingDetails indexingDetails;
-            try {
-                indexingDetails = getIndexingDetails(rule.getFormula());
-            } catch (Exception e) {
-                throw new IllegalArgumentException(
-                        String.format("Malformed rule identified. [%s]", rule.toString()));
-            }
-
-            int ruleIndexType = indexingDetails.getIndexType();
-            String ruleKey = indexingDetails.getRuleKey();
-
-            if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) {
-                typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList());
-            }
-
-            typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule);
-        }
-
-        return typeOrganizedRuleMap;
-    }
-
-    private static RuleIndexingDetails getIndexingDetails(IntegrityFormula formula) {
-        switch (formula.getTag()) {
-            case IntegrityFormula.COMPOUND_FORMULA_TAG:
-                return getIndexingDetailsForCompoundFormula((CompoundFormula) formula);
-            case IntegrityFormula.STRING_ATOMIC_FORMULA_TAG:
-                return getIndexingDetailsForStringAtomicFormula(
-                        (AtomicFormula.StringAtomicFormula) formula);
-            case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG:
-            case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
-            case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG:
-                // Package name and app certificate related formulas are string atomic formulas.
-                return new RuleIndexingDetails(NOT_INDEXED);
-            default:
-                throw new IllegalArgumentException(
-                        String.format("Invalid formula tag type: %s", formula.getTag()));
-        }
-    }
-
-    private static RuleIndexingDetails getIndexingDetailsForCompoundFormula(
-            CompoundFormula compoundFormula) {
-        int connector = compoundFormula.getConnector();
-        List<IntegrityFormula> formulas = compoundFormula.getFormulas();
-
-        switch (connector) {
-            case CompoundFormula.AND:
-            case CompoundFormula.OR:
-                // If there is a package name related atomic rule, return package name indexed.
-                Optional<RuleIndexingDetails> packageNameRule =
-                        formulas.stream()
-                                .map(formula -> getIndexingDetails(formula))
-                                .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
-                                        == PACKAGE_NAME_INDEXED)
-                                .findAny();
-                if (packageNameRule.isPresent()) {
-                    return packageNameRule.get();
-                }
-
-                // If there is an app certificate related atomic rule but no package name related
-                // atomic rule, return app certificate indexed.
-                Optional<RuleIndexingDetails> appCertificateRule =
-                        formulas.stream()
-                                .map(formula -> getIndexingDetails(formula))
-                                .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
-                                        == APP_CERTIFICATE_INDEXED)
-                                .findAny();
-                if (appCertificateRule.isPresent()) {
-                    return appCertificateRule.get();
-                }
-
-                // Do not index when there is not package name or app certificate indexing.
-                return new RuleIndexingDetails(NOT_INDEXED);
-            default:
-                // Having a NOT operator in the indexing messes up the indexing; e.g., deny
-                // installation if app certificate is NOT X (should not be indexed with app cert
-                // X). We will not keep these rules indexed.
-                // Also any other type of unknown operators will not be indexed.
-                return new RuleIndexingDetails(NOT_INDEXED);
-        }
-    }
-
-    private static RuleIndexingDetails getIndexingDetailsForStringAtomicFormula(
-            AtomicFormula.StringAtomicFormula atomicFormula) {
-        switch (atomicFormula.getKey()) {
-            case AtomicFormula.PACKAGE_NAME:
-                return new RuleIndexingDetails(PACKAGE_NAME_INDEXED, atomicFormula.getValue());
-            case AtomicFormula.APP_CERTIFICATE:
-                return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED, atomicFormula.getValue());
-            default:
-                return new RuleIndexingDetails(NOT_INDEXED);
-        }
-    }
-}
-
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
deleted file mode 100644
index 022b4b8..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
+++ /dev/null
@@ -1,52 +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.serializer;
-
-import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG;
-import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG;
-
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-/** Helper class for writing rule metadata. */
-public class RuleMetadataSerializer {
-    /** Serialize the rule metadata to an output stream. */
-    public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream)
-            throws IOException {
-        TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(outputStream);
-
-        serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider());
-        serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion());
-
-        xmlSerializer.endDocument();
-    }
-
-    private static void serializeTaggedValue(TypedXmlSerializer xmlSerializer, String tag,
-            String value) throws IOException {
-        xmlSerializer.startTag(/* namespace= */ null, tag);
-        xmlSerializer.text(value);
-        xmlSerializer.endTag(/* namespace= */ null, tag);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java
deleted file mode 100644
index 60cfc48..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.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.serializer;
-
-import android.annotation.NonNull;
-
-/**
- * Thrown when rule serialization fails.
- */
-public class RuleSerializeException extends Exception {
-    public RuleSerializeException(@NonNull String message) {
-        super(message);
-    }
-
-    public RuleSerializeException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
deleted file mode 100644
index 2941856..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
+++ /dev/null
@@ -1,39 +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.serializer;
-
-import android.content.integrity.Rule;
-
-import java.io.OutputStream;
-import java.util.List;
-import java.util.Optional;
-
-/** A helper class to serialize rules from the {@link Rule} model. */
-public interface RuleSerializer {
-
-    /** Serialize rules to an output stream */
-    void serialize(
-            List<Rule> rules,
-            Optional<Integer> formatVersion,
-            OutputStream ruleFileOutputStream,
-            OutputStream indexingFileOutputStream)
-            throws RuleSerializeException;
-
-    /** Serialize rules to a ByteArray. */
-    byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
-            throws RuleSerializeException;
-}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8798a64..c314ab0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -253,10 +253,10 @@
 
     private static final String MIGRATED_FRP2 = "migrated_frp2";
     private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
-    private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
     private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
     private static final String MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS =
             "migrated_weaver_disabled_on_unsecured_users";
+    // Note: some other migrated_* strings used to be used and may exist in the database already.
 
     // Duration that LockSettingsService will store the gatekeeper password for. This allows
     // multiple biometric enrollments without prompting the user to enter their password via
@@ -347,6 +347,8 @@
 
     private final StorageManagerInternal mStorageManagerInternal;
 
+    private final Object mGcWorkToken = new Object();
+
     // This class manages life cycle events for encrypted users on File Based Encryption (FBE)
     // devices. The most basic of these is to show/hide notifications about missing features until
     // the user unlocks the account and credential-encrypted storage is available.
@@ -1219,21 +1221,16 @@
                 Slog.i(TAG, "Synthetic password is already not protected by Weaver");
             }
         } else if (sp == null) {
-            Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
-            return;
+            throw new IllegalStateException(
+                    "Failed to unwrap synthetic password for unsecured user " + userId);
         }
 
         // Call setCeStorageProtection(), to re-encrypt the CE key with the SP if it's currently
-        // encrypted by an empty secret.  Skip this if it was definitely already done as part of the
-        // upgrade to Android 14, since while setCeStorageProtection() is idempotent it does log
-        // some error messages when called again.  Do not skip this if
-        // config_disableWeaverOnUnsecuredUsers=true, since in that case we'd like to recover from
-        // the case where an earlier upgrade to Android 14 incorrectly skipped this step.
-        if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null
-                || isWeaverDisabledOnUnsecuredUsers()) {
-            Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
-            setCeStorageProtection(userId, sp);
-        }
+        // encrypted by an empty secret.  If the CE key is already encrypted by the SP, then this is
+        // a no-op except for some log messages.
+        Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
+        setCeStorageProtection(userId, sp);
+
         Slogf.i(TAG, "Initializing Keystore super keys for user %d", userId);
         initKeystoreSuperKeys(userId, sp, /* allowExisting= */ true);
     }
@@ -3637,11 +3634,19 @@
      * release references to the argument.
      */
     private void scheduleGc() {
+        // Cancel any existing GC request first, so that GC requests don't pile up if lockscreen
+        // credential operations are happening very quickly, e.g. as sometimes happens during tests.
+        //
+        // This delays the already-requested GC, but that is fine in practice where lockscreen
+        // operations don't happen very quickly.  And the precise time that the sanitization happens
+        // isn't very important; doing it within a minute can be fine, for example.
+        mHandler.removeCallbacksAndMessages(mGcWorkToken);
+
         mHandler.postDelayed(() -> {
             System.gc();
             System.runFinalization();
             System.gc();
-        }, 2000);
+        }, mGcWorkToken, 2000);
     }
 
     private class DeviceProvisionedObserver extends ContentObserver {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 89555a9..c8a8799 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -161,6 +161,11 @@
     }
 
     @Override
+    public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) {
+        // NA as MediaSession2 doesn't support UserEngagementStates for FGS.
+    }
+
+    @Override
     public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
             KeyEvent ke, int sequenceId, ResultReceiver cb) {
         // TODO(jaewan): Implement.
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 0a9109b..0d779af 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -234,51 +234,49 @@
     private final Runnable mUserEngagementTimeoutExpirationRunnable =
             () -> {
                 synchronized (mLock) {
-                    updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+                    updateUserEngagedStateIfNeededLocked(
+                            /* isTimeoutExpired= */ true,
+                            /* isGlobalPrioritySessionActive= */ false);
                 }
             };
 
     @GuardedBy("mLock")
     private @UserEngagementState int mUserEngagementState = USER_DISENGAGED;
 
-    @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED})
+    @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARILY_ENGAGED, USER_DISENGAGED})
     @Retention(RetentionPolicy.SOURCE)
     private @interface UserEngagementState {}
 
     /**
-     * Indicates that the session is active and in one of the user engaged states.
+     * Indicates that the session is {@linkplain MediaSession#isActive() active} and in one of the
+     * {@linkplain PlaybackState#isActive() active states}.
      *
      * @see #updateUserEngagedStateIfNeededLocked(boolean)
      */
     private static final int USER_PERMANENTLY_ENGAGED = 0;
 
     /**
-     * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state.
+     * Indicates that the session is {@linkplain MediaSession#isActive() active} and has recently
+     * switched to one of the {@linkplain PlaybackState#isActive() inactive states}.
      *
      * @see #updateUserEngagedStateIfNeededLocked(boolean)
      */
-    private static final int USER_TEMPORARY_ENGAGED = 1;
+    private static final int USER_TEMPORARILY_ENGAGED = 1;
 
     /**
-     * Indicates that the session is either not active or in one of the user disengaged states
+     * Indicates that the session is either not {@linkplain MediaSession#isActive() active} or in
+     * one of the {@linkplain PlaybackState#isActive() inactive states}.
      *
      * @see #updateUserEngagedStateIfNeededLocked(boolean)
      */
     private static final int USER_DISENGAGED = 2;
 
     /**
-     * Indicates the duration of the temporary engaged states, in milliseconds.
+     * Indicates the duration of the temporary engaged state, in milliseconds.
      *
-     * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily
-     * engaged, meaning the corresponding session is only considered in an engaged state for the
-     * duration of this timeout, and only if coming from an engaged state.
-     *
-     * <p>For example, if a session is transitioning from a user-engaged state {@link
-     * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link
-     * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for
-     * the duration of this timeout, starting at the transition instant. However, a temporary
-     * user-engaged state is not considered user-engaged when transitioning from a non-user engaged
-     * state {@link PlaybackState#STATE_STOPPED}.
+     * <p>When switching to an {@linkplain PlaybackState#isActive() inactive state}, the user is
+     * treated as temporarily engaged, meaning the corresponding session is only considered in an
+     * engaged state for the duration of this timeout, and only if coming from an engaged state.
      */
     private static final int TEMP_USER_ENGAGED_TIMEOUT_MS = 600000;
 
@@ -604,7 +602,8 @@
             mSessionCb.mCb.asBinder().unlinkToDeath(this, 0);
             mDestroyed = true;
             mPlaybackState = null;
-            updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+            updateUserEngagedStateIfNeededLocked(
+                    /* isTimeoutExpired= */ true, /* isGlobalPrioritySessionActive= */ false);
             mHandler.post(MessageHandler.MSG_DESTROYED);
         }
     }
@@ -621,6 +620,24 @@
         mHandler.post(mUserEngagementTimeoutExpirationRunnable);
     }
 
+    @Override
+    public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) {
+        mHandler.post(
+                () -> {
+                    synchronized (mLock) {
+                        if (isGlobalPrioritySessionActive) {
+                            mHandler.removeCallbacks(mUserEngagementTimeoutExpirationRunnable);
+                        } else {
+                            if (mUserEngagementState == USER_TEMPORARILY_ENGAGED) {
+                                mHandler.postDelayed(
+                                        mUserEngagementTimeoutExpirationRunnable,
+                                        TEMP_USER_ENGAGED_TIMEOUT_MS);
+                            }
+                        }
+                    }
+                });
+    }
+
     /**
      * Sends media button.
      *
@@ -1111,21 +1128,20 @@
     }
 
     @GuardedBy("mLock")
-    private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) {
+    private void updateUserEngagedStateIfNeededLocked(
+            boolean isTimeoutExpired, boolean isGlobalPrioritySessionActive) {
         if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
             return;
         }
         int oldUserEngagedState = mUserEngagementState;
         int newUserEngagedState;
-        if (!isActive() || mPlaybackState == null || mDestroyed) {
+        if (!isActive() || mPlaybackState == null) {
             newUserEngagedState = USER_DISENGAGED;
-        } else if (isActive() && mPlaybackState.isActive()) {
+        } else if (mPlaybackState.isActive()) {
             newUserEngagedState = USER_PERMANENTLY_ENGAGED;
-        } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) {
-            newUserEngagedState =
-                    oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired
-                            ? USER_TEMPORARY_ENGAGED
-                            : USER_DISENGAGED;
+        } else if (oldUserEngagedState == USER_PERMANENTLY_ENGAGED
+                || (oldUserEngagedState == USER_TEMPORARILY_ENGAGED && !isTimeoutExpired)) {
+            newUserEngagedState = USER_TEMPORARILY_ENGAGED;
         } else {
             newUserEngagedState = USER_DISENGAGED;
         }
@@ -1134,7 +1150,7 @@
         }
 
         mUserEngagementState = newUserEngagedState;
-        if (newUserEngagedState == USER_TEMPORARY_ENGAGED) {
+        if (newUserEngagedState == USER_TEMPORARILY_ENGAGED && !isGlobalPrioritySessionActive) {
             mHandler.postDelayed(
                     mUserEngagementTimeoutExpirationRunnable, TEMP_USER_ENGAGED_TIMEOUT_MS);
         } else {
@@ -1189,9 +1205,11 @@
                         .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
                                 callingUid, callingPid);
             }
+            boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive();
             synchronized (mLock) {
                 mIsActive = active;
-                updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
+                updateUserEngagedStateIfNeededLocked(
+                        /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive);
             }
             long token = Binder.clearCallingIdentity();
             try {
@@ -1348,9 +1366,11 @@
             boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState)
                     || (!TRANSITION_PRIORITY_STATES.contains(oldState)
                     && TRANSITION_PRIORITY_STATES.contains(newState));
+            boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive();
             synchronized (mLock) {
                 mPlaybackState = state;
-                updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
+                updateUserEngagedStateIfNeededLocked(
+                        /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive);
             }
             final long token = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 15f90d4..6c3b123 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -206,6 +206,10 @@
      */
     public abstract void expireTempEngaged();
 
+    /** Notifies record that the global priority session active state changed. */
+    public abstract void onGlobalPrioritySessionActiveChanged(
+            boolean isGlobalPrioritySessionActive);
+
     @Override
     public final boolean equals(Object o) {
         if (this == o) return true;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 1ebc856..2b29fbd 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -362,6 +362,7 @@
                                     + record.isActive());
                 }
                 user.pushAddressedPlayerChangedLocked();
+                mHandler.post(this::notifyGlobalPrioritySessionActiveChanged);
             } else {
                 if (!user.mPriorityStack.contains(record)) {
                     Log.w(TAG, "Unknown session updated. Ignoring.");
@@ -394,11 +395,16 @@
 
     // Currently only media1 can become global priority session.
     void setGlobalPrioritySession(MediaSessionRecord record) {
+        boolean globalPrioritySessionActiveChanged = false;
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (mGlobalPrioritySession != record) {
                 Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
                         + " to " + record);
+                globalPrioritySessionActiveChanged =
+                        (mGlobalPrioritySession == null && record.isActive())
+                                || (mGlobalPrioritySession != null
+                                        && mGlobalPrioritySession.isActive() != record.isActive());
                 mGlobalPrioritySession = record;
                 if (user != null && user.mPriorityStack.contains(record)) {
                     // Handle the global priority session separately.
@@ -409,6 +415,30 @@
                 }
             }
         }
+        if (globalPrioritySessionActiveChanged) {
+            mHandler.post(this::notifyGlobalPrioritySessionActiveChanged);
+        }
+    }
+
+    /** Returns whether the global priority session is active. */
+    boolean isGlobalPrioritySessionActive() {
+        synchronized (mLock) {
+            return isGlobalPriorityActiveLocked();
+        }
+    }
+
+    private void notifyGlobalPrioritySessionActiveChanged() {
+        if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+            return;
+        }
+        synchronized (mLock) {
+            boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
+            for (Set<MediaSessionRecordImpl> records : mUserEngagedSessionsForFgs.values()) {
+                for (MediaSessionRecordImpl record : records) {
+                    record.onGlobalPrioritySessionActiveChanged(isGlobalPriorityActive);
+                }
+            }
+        }
     }
 
     private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
@@ -646,8 +676,11 @@
 
         if (mGlobalPrioritySession == session) {
             mGlobalPrioritySession = null;
-            if (session.isActive() && user != null) {
-                user.pushAddressedPlayerChangedLocked();
+            if (session.isActive()) {
+                if (user != null) {
+                    user.pushAddressedPlayerChangedLocked();
+                }
+                mHandler.post(this::notifyGlobalPrioritySessionActiveChanged);
             }
         } else {
             if (user != null) {
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index a492a72..6ce8685 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.net.IpSecTransformState;
 import android.net.vcn.FeatureFlags;
 import android.net.vcn.FeatureFlagsImpl;
 import android.os.Looper;
@@ -70,19 +69,6 @@
         return mIsInTestMode;
     }
 
-    public boolean isFlagIpSecTransformStateEnabled() {
-        // TODO: b/328844044: Ideally this code should gate the behavior by checking the
-        // android.net.platform.flags.ipsec_transform_state flag but that flag is not accessible
-        // right now. We should either update the code when the flag is accessible or remove the
-        // legacy behavior after VIC SDK finalization
-        try {
-            new IpSecTransformState.Builder();
-            return true;
-        } catch (Exception e) {
-            return false;
-        }
-    }
-
     @NonNull
     public FeatureFlags getFeatureFlags() {
         return mFeatureFlags;
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 189b608..2d3bc84 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1912,8 +1912,7 @@
                 // Transforms do not need to be persisted; the IkeSession will keep them alive
                 mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
 
-                if (direction == IpSecManager.DIRECTION_IN
-                        && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                if (direction == IpSecManager.DIRECTION_IN) {
                     mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
                 }
 
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 6f1e15b..16ab51e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -148,12 +148,6 @@
 
         Objects.requireNonNull(deps, "Missing deps");
 
-        if (!vcnContext.isFlagIpSecTransformStateEnabled()) {
-            // Caller error
-            logWtf("ipsecTransformState flag disabled");
-            throw new IllegalAccessException("ipsecTransformState flag disabled");
-        }
-
         mHandler = new Handler(getVcnContext().getLooper());
 
         mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class);
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 0b9b677..3eeeece 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -204,10 +204,8 @@
         List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
         mCellBringupCallbacks.clear();
 
-        if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
-            for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
-                evaluator.close();
-            }
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            evaluator.close();
         }
 
         mUnderlyingNetworkRecords.clear();
@@ -429,10 +427,7 @@
         if (oldSnapshot
                 .getAllSubIdsInGroup(mSubscriptionGroup)
                 .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
-
-            if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
-                reevaluateNetworks();
-            }
+            reevaluateNetworks();
             return;
         }
         registerOrUpdateNetworkRequests();
@@ -445,11 +440,6 @@
      */
     public void updateInboundTransform(
             @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
-        if (!mVcnContext.isFlagIpSecTransformStateEnabled()) {
-            logWtf("#updateInboundTransform: unexpected call; flags missing");
-            return;
-        }
-
         Objects.requireNonNull(currentNetwork, "currentNetwork is null");
         Objects.requireNonNull(transform, "transform is null");
 
@@ -572,10 +562,7 @@
 
         @Override
         public void onLost(@NonNull Network network) {
-            if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
-                mUnderlyingNetworkRecords.get(network).close();
-            }
-
+            mUnderlyingNetworkRecords.get(network).close();
             mUnderlyingNetworkRecords.remove(network);
 
             reevaluateNetworks();
@@ -648,11 +635,6 @@
     class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
         @Override
         public void onEvaluationResultChanged() {
-            if (!mVcnContext.isFlagIpSecTransformStateEnabled()) {
-                logWtf("#onEvaluationResultChanged: unexpected call; flags missing");
-                return;
-            }
-
             mVcnContext.ensureRunningOnLooperThread();
             reevaluateNetworks();
         }
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index 448a7eb..08be11e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -102,17 +102,15 @@
         updatePriorityClass(
                 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
 
-        if (isIpSecPacketLossDetectorEnabled()) {
-            try {
-                mMetricMonitors.add(
-                        mDependencies.newIpSecPacketLossDetector(
-                                mVcnContext,
-                                mNetworkRecordBuilder.getNetwork(),
-                                carrierConfig,
-                                new MetricMonitorCallbackImpl()));
-            } catch (IllegalAccessException e) {
-                // No action. Do not add anything to mMetricMonitors
-            }
+        try {
+            mMetricMonitors.add(
+                    mDependencies.newIpSecPacketLossDetector(
+                            mVcnContext,
+                            mNetworkRecordBuilder.getNetwork(),
+                            carrierConfig,
+                            new MetricMonitorCallbackImpl()));
+        } catch (IllegalAccessException e) {
+            // No action. Do not add anything to mMetricMonitors
         }
     }
 
@@ -188,22 +186,12 @@
         }
     }
 
-    private boolean isIpSecPacketLossDetectorEnabled() {
-        return isIpSecPacketLossDetectorEnabled(mVcnContext);
-    }
-
-    private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) {
-        return vcnContext.isFlagIpSecTransformStateEnabled();
-    }
-
     /** Get the comparator for UnderlyingNetworkEvaluator */
     public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) {
         return (left, right) -> {
-            if (isIpSecPacketLossDetectorEnabled(vcnContext)) {
-                if (left.mIsPenalized != right.mIsPenalized) {
-                    // A penalized network should have lower priority which means a larger index
-                    return left.mIsPenalized ? 1 : -1;
-                }
+            if (left.mIsPenalized != right.mIsPenalized) {
+                // A penalized network should have lower priority which means a larger index
+                return left.mIsPenalized ? 1 : -1;
             }
 
             final int leftIndex = left.mPriorityClass;
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 043470f..9ee42b1 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -84,8 +84,13 @@
         mWebViewUpdatedReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
+                    final String action = intent.getAction();
+                    if (action == null) {
+                        return;
+                    }
+
                     int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-                    switch (intent.getAction()) {
+                    switch (action) {
                         case Intent.ACTION_PACKAGE_REMOVED:
                             // When a package is replaced we will receive two intents, one
                             // representing the removal of the old package and one representing the
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e6d8132..d2a128b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -938,7 +938,6 @@
         mLastStartReason = request.reason;
         mLastStartActivityTimeMs = System.currentTimeMillis();
 
-        final ActivityRecord previousStart = mLastStartActivityRecord;
         final IApplicationThread caller = request.caller;
         Intent intent = request.intent;
         NeededUriGrants intentGrants = request.intentGrants;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 7a710dc..662f916 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -55,7 +55,6 @@
         "com_android_server_powerstats_PowerStatsService.cpp",
         "com_android_server_power_stats_CpuPowerStatsCollector.cpp",
         "com_android_server_hint_HintManagerService.cpp",
-        "com_android_server_SerialService.cpp",
         "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
         "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
         "com_android_server_stats_pull_StatsPullAtomService.cpp",
diff --git a/services/core/jni/com_android_server_SerialService.cpp b/services/core/jni/com_android_server_SerialService.cpp
deleted file mode 100644
index 6600c98..0000000
--- a/services/core/jni/com_android_server_SerialService.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "SerialServiceJNI"
-#include "utils/Log.h"
-
-#include "jni.h"
-#include <nativehelper/JNIPlatformHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-namespace android
-{
-
-static struct parcel_file_descriptor_offsets_t
-{
-    jclass mClass;
-    jmethodID mConstructor;
-} gParcelFileDescriptorOffsets;
-
-static jobject android_server_SerialService_open(JNIEnv *env, jobject /* thiz */, jstring path)
-{
-    const char *pathStr = env->GetStringUTFChars(path, NULL);
-
-    int fd = open(pathStr, O_RDWR | O_NOCTTY);
-    if (fd < 0) {
-        ALOGE("could not open %s", pathStr);
-        env->ReleaseStringUTFChars(path, pathStr);
-        return NULL;
-    }
-    env->ReleaseStringUTFChars(path, pathStr);
-
-    jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
-    if (fileDescriptor == NULL) {
-        close(fd);
-        return NULL;
-    }
-    return env->NewObject(gParcelFileDescriptorOffsets.mClass,
-        gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
-}
-
-
-static const JNINativeMethod method_table[] = {
-    { "native_open",                "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
-                                    (void*)android_server_SerialService_open },
-};
-
-int register_android_server_SerialService(JNIEnv *env)
-{
-    jclass clazz = env->FindClass("com/android/server/SerialService");
-    if (clazz == NULL) {
-        ALOGE("Can't find com/android/server/SerialService");
-        return -1;
-    }
-
-    clazz = env->FindClass("android/os/ParcelFileDescriptor");
-    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
-    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
-    gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
-    LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL,
-                 "Unable to find constructor for android.os.ParcelFileDescriptor");
-
-    return jniRegisterNativeMethods(env, "com/android/server/SerialService",
-            method_table, NELEM(method_table));
-}
-
-};
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 6464081..f9633e2 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -33,7 +33,6 @@
 int register_android_server_power_stats_CpuPowerStatsCollector(JNIEnv* env);
 int register_android_server_HintManagerService(JNIEnv* env);
 int register_android_server_storage_AppFuse(JNIEnv* env);
-int register_android_server_SerialService(JNIEnv* env);
 int register_android_server_SystemServer(JNIEnv* env);
 int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
 int register_android_server_UsbAlsaMidiDevice(JNIEnv* env);
@@ -93,7 +92,6 @@
     register_android_server_PowerStatsService(env);
     register_android_server_power_stats_CpuPowerStatsCollector(env);
     register_android_server_HintManagerService(env);
-    register_android_server_SerialService(env);
     register_android_server_InputManager(env);
     register_android_server_LightsService(env);
     register_android_server_UsbDeviceManager(env);
diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index dc5cb8d6..0c9f70c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -1,6 +1,6 @@
 per-file *Alarm* = file:/apex/jobscheduler/OWNERS
 per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS
-per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
+per-file *DeviceIdleController* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS
 per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS
 per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS
 per-file *Storage* = file:/core/java/android/os/storage/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
index 4fac647..809b7bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
@@ -2,3 +2,4 @@
 
 per-file ApplicationStartInfoTest.java = yforta@google.com, carmenjackson@google.com, jji@google.com
 per-file CachedAppOptimizerTest.java = file:/PERFORMANCE_OWNERS
+per-file BaseBroadcastQueueTest.java = file:/BROADCASTS_OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
deleted file mode 100644
index 9ed2e88..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ /dev/null
@@ -1,914 +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.serializer;
-
-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.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.NONINDEXED_RULE_SIZE_LIMIT;
-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.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-@RunWith(JUnit4.class)
-public class RuleBinarySerializerTest {
-
-    private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
-    private static final String SAMPLE_INSTALLER_CERT = "installer_cert";
-
-    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 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 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 INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS);
-    private static final String INSTALLER_CERTIFICATE =
-            getBits(AtomicFormula.INSTALLER_CERTIFICATE, 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 String EQ = getBits(AtomicFormula.EQ, 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 String START_BIT = "1";
-    private static final String END_BIT = "1";
-
-    private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
-            getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
-
-    private static final String SERIALIZED_START_INDEXING_KEY =
-            IS_NOT_HASHED
-                    + getBits(START_INDEXING_KEY.length(), VALUE_SIZE_BITS)
-                    + getValueBits(START_INDEXING_KEY);
-    private static final String SERIALIZED_END_INDEXING_KEY =
-            IS_NOT_HASHED
-                    + getBits(END_INDEXING_KEY.length(), VALUE_SIZE_BITS)
-                    + getValueBits(END_INDEXING_KEY);
-
-    @Test
-    public void testBinaryString_serializeNullRules() {
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                /* expectedExceptionMessageRegex= */ "Null rules cannot be serialized.",
-                () -> binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
-    }
-
-    @Test
-    public void testBinaryString_emptyRules() throws Exception {
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        binarySerializer.serialize(
-                Collections.emptyList(),
-                /* formatVersion= */ Optional.empty(),
-                ruleOutputStream,
-                indexingOutputStream);
-
-        ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
-        expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        assertThat(ruleOutputStream.toByteArray())
-                .isEqualTo(expectedRuleOutputStream.toByteArray());
-
-        ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-        String serializedIndexingBytes =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
-                        + SERIALIZED_END_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
-        byte[] expectedIndexingBytes =
-                getBytes(
-                        serializedIndexingBytes
-                                + serializedIndexingBytes
-                                + serializedIndexingBytes);
-        expectedIndexingOutputStream.write(expectedIndexingBytes);
-        assertThat(indexingOutputStream.toByteArray())
-                .isEqualTo(expectedIndexingOutputStream.toByteArray());
-    }
-
-    @Test
-    public void testBinaryStream_serializeValidCompoundFormula() throws Exception {
-        String packageName = "com.test.app";
-        Rule rule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Collections.singletonList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        binarySerializer.serialize(
-                Collections.singletonList(rule),
-                /* formatVersion= */ Optional.empty(),
-                ruleOutputStream,
-                indexingOutputStream);
-
-        String expectedBits =
-                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;
-        ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
-        expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        expectedRuleOutputStream.write(getBytes(expectedBits));
-        assertThat(ruleOutputStream.toByteArray())
-                .isEqualTo(expectedRuleOutputStream.toByteArray());
-
-        ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-        String expectedIndexingBitsForIndexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
-                        + SERIALIZED_END_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
-        String expectedIndexingBitsForUnindexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
-                        + SERIALIZED_END_INDEXING_KEY
-                        + getBits(
-                                DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
-                                /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(
-                getBytes(
-                        expectedIndexingBitsForIndexed
-                                + expectedIndexingBitsForIndexed
-                                + expectedIndexingBitsForUnindexed));
-
-        assertThat(indexingOutputStream.toByteArray())
-                .isEqualTo(expectedIndexingOutputStream.toByteArray());
-    }
-
-    @Test
-    public void testBinaryString_serializeValidCompoundFormula_notConnector() throws Exception {
-        String packageName = "com.test.app";
-        Rule rule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Collections.singletonList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                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;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidCompoundFormula_andConnector() throws Exception {
-        String packageName = "com.test.app";
-        String appCertificate = "test_cert";
-        Rule rule =
-                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);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                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;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidCompoundFormula_orConnector() throws Exception {
-        String packageName = "com.test.app";
-        String appCertificate = "test_cert";
-        Rule rule =
-                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);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                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;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_stringValue() throws Exception {
-        String packageName = "com.test.app";
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.PACKAGE_NAME,
-                                packageName,
-                                /* isHashedValue= */ false),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_hashedValue() throws Exception {
-        String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.APP_CERTIFICATE,
-                                IntegrityUtils.getHexDigest(
-                                        appCertificate.getBytes(StandardCharsets.UTF_8)),
-                                /* isHashedValue= */ true),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_integerValue() throws Exception {
-        long versionCode = 1;
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.LongAtomicFormula(
-                                AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + VERSION_CODE
-                        + EQ
-                        + getBits(versionCode, /* numOfBits= */ 64)
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_booleanValue() throws Exception {
-        String preInstalled = "1";
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PRE_INSTALLED
-                        + EQ
-                        + preInstalled
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeInvalidFormulaType() throws Exception {
-        IntegrityFormula invalidFormula = getInvalidFormula();
-        Rule rule = new Rule(invalidFormula, Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
-                () ->
-                        binarySerializer.serialize(
-                                Collections.singletonList(rule),
-                                /* formatVersion= */ Optional.empty()));
-    }
-
-    @Test
-    public void testBinaryString_serializeFormatVersion() throws Exception {
-        int formatVersion = 1;
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits = getBits(formatVersion, FORMAT_VERSION_BITS);
-        byte[] expectedRules = getBytes(expectedBits);
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.emptyList(), /* formatVersion= */ Optional.of(formatVersion));
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_verifyManyRulesAreIndexedCorrectly() throws Exception {
-        int ruleCount = 225;
-        String packagePrefix = "package.name.";
-        String appCertificatePrefix = "app.cert.";
-        String installerNamePrefix = "installer.";
-
-        // Create the rule set with 225 package name based rules, 225 app certificate indexed rules,
-        // and 225 non-indexed rules..
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithPackageNameAndSampleInstallerName(
-                            String.format("%s%04d", packagePrefix, count)));
-        }
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithAppCertificateAndSampleInstallerName(
-                            String.format("%s%04d", appCertificatePrefix, count)));
-        }
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getNonIndexedRuleWithInstallerName(
-                            String.format("%s%04d", installerNamePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        binarySerializer.serialize(
-                ruleList,
-                /* formatVersion= */ Optional.empty(),
-                ruleOutputStream,
-                indexingOutputStream);
-
-        // Verify the rules file and index files.
-        ByteArrayOutputStream expectedOrderedRuleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-
-        expectedOrderedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
-
-        String expectedIndexingBytesForPackageNameIndexed =
-                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        for (int count = 0; count < ruleCount; count++) {
-            String packageName = String.format("%s%04d", packagePrefix, count);
-            if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
-                expectedIndexingBytesForPackageNameIndexed +=
-                        IS_NOT_HASHED
-                                + getBits(packageName.length(), VALUE_SIZE_BITS)
-                                + getValueBits(packageName)
-                                + getBits(totalBytesWritten, /* numOfBits= */ 32);
-            }
-
-            byte[] bytesForPackage =
-                    getBytes(
-                            getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
-                                    packageName));
-            expectedOrderedRuleOutputStream.write(bytesForPackage);
-            totalBytesWritten += bytesForPackage.length;
-        }
-        expectedIndexingBytesForPackageNameIndexed +=
-                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
-        String expectedIndexingBytesForAppCertificateIndexed =
-                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        for (int count = 0; count < ruleCount; count++) {
-            String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
-            if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
-                expectedIndexingBytesForAppCertificateIndexed +=
-                        IS_NOT_HASHED
-                                + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                                + getValueBits(appCertificate)
-                                + getBits(totalBytesWritten, /* numOfBits= */ 32);
-            }
-
-            byte[] bytesForPackage =
-                    getBytes(
-                            getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
-                                    appCertificate));
-            expectedOrderedRuleOutputStream.write(bytesForPackage);
-            totalBytesWritten += bytesForPackage.length;
-        }
-        expectedIndexingBytesForAppCertificateIndexed +=
-                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
-        String expectedIndexingBytesForUnindexed =
-                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        for (int count = 0; count < ruleCount; count++) {
-            byte[] bytesForPackage =
-                    getBytes(
-                            getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
-                                    String.format("%s%04d", installerNamePrefix, count)));
-            expectedOrderedRuleOutputStream.write(bytesForPackage);
-            totalBytesWritten += bytesForPackage.length;
-        }
-        expectedIndexingBytesForUnindexed +=
-                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(
-                getBytes(
-                        expectedIndexingBytesForPackageNameIndexed
-                                + expectedIndexingBytesForAppCertificateIndexed
-                                + expectedIndexingBytesForUnindexed));
-
-        assertThat(ruleOutputStream.toByteArray())
-                .isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
-        assertThat(indexingOutputStream.toByteArray())
-                .isEqualTo(expectedIndexingOutputStream.toByteArray());
-    }
-
-    @Test
-    public void testBinaryString_totalRuleSizeLimitReached() {
-        int ruleCount = INDEXED_RULE_SIZE_LIMIT - 1;
-        String packagePrefix = "package.name.";
-        String appCertificatePrefix = "app.cert.";
-        String installerNamePrefix = "installer.";
-
-        // Create the rule set with more rules than the system can handle in total.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithPackageNameAndSampleInstallerName(
-                            String.format("%s%04d", packagePrefix, count)));
-        }
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithAppCertificateAndSampleInstallerName(
-                            String.format("%s%04d", appCertificatePrefix, count)));
-        }
-        for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT - 1; count++) {
-            ruleList.add(
-                    getNonIndexedRuleWithInstallerName(
-                            String.format("%s%04d", installerNamePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    @Test
-    public void testBinaryString_tooManyPackageNameIndexedRules() {
-        String packagePrefix = "package.name.";
-
-        // Create a rule set with too many package name indexed rules.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
-            ruleList.add(
-                    getRuleWithPackageNameAndSampleInstallerName(
-                            String.format("%s%04d", packagePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided in the indexing group.",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    @Test
-    public void testBinaryString_tooManyAppCertificateIndexedRules() {
-        String appCertificatePrefix = "app.cert.";
-
-        // Create a rule set with too many app certificate indexed rules.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
-            ruleList.add(
-                    getRuleWithAppCertificateAndSampleInstallerName(
-                            String.format("%s%04d", appCertificatePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided in the indexing group.",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    @Test
-    public void testBinaryString_tooManyNonIndexedRules() {
-        String installerNamePrefix = "installer.";
-
-        // Create a rule set with too many unindexed rules.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT + 1; count++) {
-            ruleList.add(
-                    getNonIndexedRuleWithInstallerName(
-                            String.format("%s%04d", installerNamePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided in the indexing group.",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.PACKAGE_NAME,
-                                        packageName,
-                                        /* isHashedValue= */ false),
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_NAME,
-                                        SAMPLE_INSTALLER_NAME,
-                                        /* isHashedValue= */ false))),
-                Rule.DENY);
-    }
-
-    private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
-            String packageName) {
-        return 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
-                + INSTALLER_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
-                + getValueBits(SAMPLE_INSTALLER_NAME)
-                + COMPOUND_FORMULA_END_BITS
-                + DENY
-                + END_BIT;
-    }
-
-    private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.APP_CERTIFICATE,
-                                        certificate,
-                                        /* isHashedValue= */ false),
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_NAME,
-                                        SAMPLE_INSTALLER_NAME,
-                                        /* isHashedValue= */ false))),
-                Rule.DENY);
-    }
-
-    private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
-            String appCertificate) {
-        return START_BIT
-                + COMPOUND_FORMULA_START_BITS
-                + AND
-                + ATOMIC_FORMULA_START_BITS
-                + APP_CERTIFICATE
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                + getValueBits(appCertificate)
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
-                + getValueBits(SAMPLE_INSTALLER_NAME)
-                + COMPOUND_FORMULA_END_BITS
-                + DENY
-                + END_BIT;
-    }
-
-    private Rule getNonIndexedRuleWithInstallerName(String installerName) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_NAME,
-                                        installerName,
-                                        /* isHashedValue= */ false),
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_CERTIFICATE,
-                                        SAMPLE_INSTALLER_CERT,
-                                        /* isHashedValue= */ false))),
-                Rule.DENY);
-    }
-
-    private String getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
-            String installerName) {
-        return START_BIT
-                + COMPOUND_FORMULA_START_BITS
-                + AND
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(installerName.length(), VALUE_SIZE_BITS)
-                + getValueBits(installerName)
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_CERTIFICATE
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS)
-                + getValueBits(SAMPLE_INSTALLER_CERT)
-                + COMPOUND_FORMULA_END_BITS
-                + DENY
-                + END_BIT;
-    }
-
-    private static IntegrityFormula getInvalidFormula() {
-        return new AtomicFormula(0) {
-            @Override
-            public int getTag() {
-                return 0;
-            }
-
-            @Override
-            public boolean matches(AppInstallMetadata appInstallMetadata) {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateLineageFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isInstallerFormula() {
-                return false;
-            }
-
-            @Override
-            public int hashCode() {
-                return super.hashCode();
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                return super.equals(obj);
-            }
-
-            @NonNull
-            @Override
-            protected Object clone() throws CloneNotSupportedException {
-                return super.clone();
-            }
-
-            @Override
-            public String toString() {
-                return super.toString();
-            }
-
-            @Override
-            protected void finalize() throws Throwable {
-                super.finalize();
-            }
-        };
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
deleted file mode 100644
index 6dccdf5..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ /dev/null
@@ -1,344 +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.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
-@RunWith(JUnit4.class)
-public class RuleIndexingDetailsIdentifierTest {
-
-    private static final String SAMPLE_APP_CERTIFICATE = "testcert";
-    private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
-    private static final String SAMPLE_INSTALLER_CERTIFICATE = "installercert";
-    private static final String SAMPLE_PACKAGE_NAME = "com.test.package";
-
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_PACKAGE_NAME =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.PACKAGE_NAME,
-                    SAMPLE_PACKAGE_NAME,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_APP_CERTIFICATE =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.APP_CERTIFICATE,
-                    SAMPLE_APP_CERTIFICATE,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_NAME =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.INSTALLER_NAME,
-                    SAMPLE_INSTALLER_NAME,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.INSTALLER_CERTIFICATE,
-                    SAMPLE_INSTALLER_CERTIFICATE,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_VERSION_CODE =
-            new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE,
-                    AtomicFormula.EQ, 12);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_ISPREINSTALLED =
-            new AtomicFormula.BooleanAtomicFormula(
-                    AtomicFormula.PRE_INSTALLED, /* booleanValue= */
-                    true);
-
-
-    private static final Rule RULE_WITH_PACKAGE_NAME =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_PACKAGE_NAME,
-                                    ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                    Rule.DENY);
-    private static final Rule RULE_WITH_APP_CERTIFICATE =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_APP_CERTIFICATE,
-                                    ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                    Rule.DENY);
-    private static final Rule RULE_WITH_INSTALLER_RESTRICTIONS =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_INSTALLER_NAME,
-                                    ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE)),
-                    Rule.DENY);
-
-    private static final Rule RULE_WITH_NONSTRING_RESTRICTIONS =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_VERSION_CODE,
-                                    ATOMIC_FORMULA_WITH_ISPREINSTALLED)),
-                    Rule.DENY);
-    public static final int INVALID_FORMULA_TAG = -1;
-
-    @Test
-    public void getIndexType_nullRule() {
-        List<Rule> ruleList = null;
-
-        assertExpectException(
-                IllegalArgumentException.class,
-                /* expectedExceptionMessageRegex= */
-                "Index buckets cannot be created for null rule list.",
-                () -> splitRulesIntoIndexBuckets(ruleList));
-    }
-
-    @Test
-    public void getIndexType_invalidFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(new Rule(getInvalidFormula(), Rule.DENY));
-
-        assertExpectException(
-                IllegalArgumentException.class,
-                /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
-                () -> splitRulesIntoIndexBuckets(ruleList));
-    }
-
-    @Test
-    public void getIndexType_ruleContainingPackageNameFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_PACKAGE_NAME);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        // Verify the resulting map content.
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(NOT_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME);
-        assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME))
-                .containsExactly(RULE_WITH_PACKAGE_NAME);
-    }
-
-    @Test
-    public void getIndexType_ruleContainingAppCertificateFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_APP_CERTIFICATE);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(NOT_INDEXED)).isEmpty();
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet())
-                .containsExactly(SAMPLE_APP_CERTIFICATE);
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE))
-                .containsExactly(RULE_WITH_APP_CERTIFICATE);
-    }
-
-    @Test
-    public void getIndexType_ruleWithUnindexedCompoundFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(NOT_INDEXED).get("N/A"))
-                .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS);
-    }
-
-    @Test
-    public void getIndexType_ruleContainingCompoundFormulaWithIntAndBoolean() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(NOT_INDEXED).get("N/A"))
-                .containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS);
-    }
-
-    @Test
-    public void getIndexType_negatedRuleContainingPackageNameFormula() {
-        Rule negatedRule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Arrays.asList(
-                                        new CompoundFormula(
-                                                CompoundFormula.AND,
-                                                Arrays.asList(
-                                                        ATOMIC_FORMULA_WITH_PACKAGE_NAME,
-                                                        ATOMIC_FORMULA_WITH_APP_CERTIFICATE)))),
-                        Rule.DENY);
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(negatedRule);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(negatedRule);
-    }
-
-    @Test
-    public void getIndexType_allRulesTogetherSplitCorrectly() {
-        Rule packageNameRuleA = getRuleWithPackageName("aaa");
-        Rule packageNameRuleB = getRuleWithPackageName("bbb");
-        Rule packageNameRuleC = getRuleWithPackageName("ccc");
-        Rule certificateRule1 = getRuleWithAppCertificate("cert1");
-        Rule certificateRule2 = getRuleWithAppCertificate("cert2");
-        Rule certificateRule3 = getRuleWithAppCertificate("cert3");
-
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(packageNameRuleB);
-        ruleList.add(packageNameRuleC);
-        ruleList.add(packageNameRuleA);
-        ruleList.add(certificateRule3);
-        ruleList.add(certificateRule2);
-        ruleList.add(certificateRule1);
-        ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
-        ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-
-        // We check asserts this way to ensure ordering based on package name.
-        assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc");
-
-        // We check asserts this way to ensure ordering based on app certificate.
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2",
-                "cert3");
-
-        assertThat(result.get(NOT_INDEXED).get("N/A"))
-                .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS,
-                        RULE_WITH_NONSTRING_RESTRICTIONS);
-    }
-
-    private Rule getRuleWithPackageName(String packageName) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.PACKAGE_NAME,
-                                        packageName,
-                                        /* isHashedValue= */ false),
-                                ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                Rule.DENY);
-    }
-
-    private Rule getRuleWithAppCertificate(String certificate) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.APP_CERTIFICATE,
-                                        certificate,
-                                        /* isHashedValue= */ false),
-                                ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                Rule.DENY);
-    }
-
-    private IntegrityFormula getInvalidFormula() {
-        return new AtomicFormula(0) {
-            @Override
-            public int getTag() {
-                return INVALID_FORMULA_TAG;
-            }
-
-            @Override
-            public boolean matches(AppInstallMetadata appInstallMetadata) {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateLineageFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isInstallerFormula() {
-                return false;
-            }
-
-            @Override
-            public int hashCode() {
-                return super.hashCode();
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                return super.equals(obj);
-            }
-
-            @NonNull
-            @Override
-            protected Object clone() throws CloneNotSupportedException {
-                return super.clone();
-            }
-
-            @Override
-            public String toString() {
-                return super.toString();
-            }
-
-            @Override
-            protected void finalize() throws Throwable {
-                super.finalize();
-            }
-        };
-    }
-}
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index d35dbb56..2dff392 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,9 +1,9 @@
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
 anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
 badhri@google.com
 elaurent@google.com
 albertccwang@google.com
 jameswei@google.com
-howardyen@google.com
\ No newline at end of file
+howardyen@google.com
+kumarashishg@google.com
\ No newline at end of file
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index e045f10..4c7b25a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -223,7 +223,6 @@
         doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
         doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
         doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
-        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index bc7ff47..441b780 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -20,7 +20,6 @@
 import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
 
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -127,8 +126,6 @@
                                 false /* isInTestMode */));
         doNothing().when(mVcnContext).ensureRunningOnLooperThread();
 
-        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
-
         setupSystemService(
                 mContext,
                 mConnectivityManager,
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index 6f31d8d..e540932 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -226,7 +226,6 @@
     private void resetVcnContext(VcnContext vcnContext) {
         reset(vcnContext);
         doNothing().when(vcnContext).ensureRunningOnLooperThread();
-        doReturn(true).when(vcnContext).isFlagIpSecTransformStateEnabled();
     }
 
     // Package private for use in NetworkPriorityClassifierTest
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index 590f719..e6d0a3d 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -58,6 +58,7 @@
         "junit",
         "objenesis",
         "mockito",
+        "systemfeatures-gen-lib",
         "truth",
     ],
 }
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index 196b5e7..1abe77f 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -71,7 +71,7 @@
         println("Usage: SystemFeaturesGenerator <outputClassName> [options]")
         println(" Options:")
         println("  --readonly=true|false    Whether to encode features as build-time constants")
-        println("  --feature=\$NAME:\$VER   A feature+version pair, where \$VER can be:")
+        println("  --feature=\$NAME:\$VER     A feature+version pair, where \$VER can be:")
         println("                             * blank/empty == undefined (variable API)")
         println("                             * valid int   == enabled   (constant API)")
         println("                             * UNAVAILABLE == disabled  (constant API)")
@@ -89,6 +89,17 @@
     /** Main entrypoint for build-time system feature codegen. */
     @JvmStatic
     fun main(args: Array<String>) {
+        generate(args, System.out)
+    }
+
+    /**
+     * Simple API entrypoint for build-time system feature codegen.
+     *
+     * Note: Typically this would be implemented in terms of a proper Builder-type input argument,
+     * but it's primarily used for testing as opposed to direct production usage.
+     */
+    @JvmStatic
+    fun generate(args: Array<String>, output: Appendable) {
         if (args.size < 1) {
             usage()
             return
@@ -155,7 +166,7 @@
             .addFileComment("This file is auto-generated. DO NOT MODIFY.\n")
             .addFileComment("Args: ${args.joinToString(" \\\n           ")}")
             .build()
-            .writeTo(System.out)
+            .writeTo(output)
     }
 
     /*
@@ -171,12 +182,27 @@
         return when (featureArgs.getOrNull(1)) {
             null, "" -> FeatureInfo(name, null, readonly = false)
             "UNAVAILABLE" -> FeatureInfo(name, null, readonly = true)
-            else -> FeatureInfo(name, featureArgs[1].toIntOrNull(), readonly = true)
+            else -> {
+                val featureVersion =
+                    featureArgs[1].toIntOrNull()
+                        ?: throw IllegalArgumentException(
+                            "Invalid feature version input for $name: ${featureArgs[1]}"
+                        )
+                FeatureInfo(name, featureArgs[1].toInt(), readonly = true)
+            }
         }
     }
 
     private fun parseFeatureName(name: String): String =
-        if (name.startsWith("FEATURE_")) name else "FEATURE_$name"
+        when {
+            name.startsWith("android") ->
+                throw IllegalArgumentException(
+                    "Invalid feature name input: \"android\"-namespaced features must be " +
+                    "provided as PackageManager.FEATURE_* suffixes, not raw feature strings."
+                )
+            name.startsWith("FEATURE_") -> name
+            else -> "FEATURE_$name"
+        }
 
     /*
      * Adds per-feature query methods to the class with the form:
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java
new file mode 100644
index 0000000..f8c585d
--- /dev/null
+++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemfeatures;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.IOException;
+
+// Note: This is a very simple argument test to validate certain behaviors for
+// invalid arguments. Correctness and validity is largely exercised by
+// SystemFeaturesGeneratorTest.
+@RunWith(JUnit4.class)
+public class SystemFeaturesGeneratorApiTest {
+
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock private Appendable mOut;
+
+    @Test
+    public void testEmpty() throws IOException {
+        final String[] args = new String[] {};
+        // This should just print the commandline and return.
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, never()).append(any());
+    }
+
+    @Test
+    public void testBasic() throws IOException {
+        final String[] args = new String[] {
+            "com.foo.Features",
+            "--feature=TELEVISION:0",
+        };
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, atLeastOnce()).append(any());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidFeatureVersion() throws IOException {
+        final String[] args = new String[] {
+            "com.foo.Features",
+            "--feature=TELEVISION:blarg",
+        };
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, never()).append(any());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidFeatureNameFromAndroidNamespace() throws IOException {
+        final String[] args = new String[] {
+            "com.foo.Features",
+            "--feature=android.hardware.doesntexist:0",
+        };
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, never()).append(any());
+    }
+}