Merge changes from topic "oom-ams-subreason" into main

* changes:
  servicestest: add jni dependency
  mockingservicestest: adding jni dependency for OomConnection
  Add out-of-memory (OOM) kills to ApplicationExitInfo
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 02fee10..c76be6f 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -13,20 +13,47 @@
 // limitations under the License.
 
 aconfig_srcjars = [
+    // !!! KEEP THIS LIST ALPHABETICAL !!!
+    ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}",
     ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
     ":android.content.res.flags-aconfig-java{.generated_srcjars}",
+    ":android.crashrecovery.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
+    ":android.media.playback.flags-aconfig-java{.generated_srcjars}",
     ":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
     ":android.nfc.flags-aconfig-java{.generated_srcjars}",
     ":android.os.flags-aconfig-java{.generated_srcjars}",
     ":android.security.flags-aconfig-java{.generated_srcjars}",
     ":com.android.hardware.camera2-aconfig-java{.generated_srcjars}",
-    ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
     ":com.android.hardware.input-aconfig-java{.generated_srcjars}",
-    ":com.android.text.flags-aconfig-java{.generated_srcjars}",
     ":com.android.net.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.text.flags-aconfig-java{.generated_srcjars}",
+    ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
+    // !!! KEEP THIS LIST ALPHABETICAL !!!
 ]
 
+stubs_defaults {
+    name: "framework-minus-apex-aconfig-declarations",
+    aconfig_declarations: [
+        "android.content.pm.flags-aconfig",
+        "android.content.res.flags-aconfig",
+        "android.crashrecovery.flags-aconfig",
+        "android.hardware.biometrics.flags-aconfig",
+        "android.media.playback.flags-aconfig",
+        "android.net.vcn.flags-aconfig",
+        "android.nfc.flags-aconfig",
+        "android.os.flags-aconfig",
+        "android.security.flags-aconfig",
+        "com.android.hardware.camera2-aconfig",
+        "com.android.hardware.input.input-aconfig",
+        "com.android.net.thread.flags-aconfig",
+        "com.android.window.flags.window-aconfig",
+        "com.android.text.flags-aconfig",
+        "com.android.net.flags-aconfig",
+    ],
+}
+
 filegroup {
     name: "framework-minus-apex-aconfig-srcjars",
     srcs: aconfig_srcjars,
@@ -119,7 +146,7 @@
 aconfig_declarations {
     name: "android.nfc.flags-aconfig",
     package: "android.nfc",
-    srcs: ["core/java/android/nfc/*.aconfig"],
+    srcs: ["nfc/java/android/nfc/*.aconfig"],
 }
 
 cc_aconfig_library {
@@ -137,7 +164,7 @@
 java_aconfig_library {
     name: "android.nfc.flags-aconfig-java",
     aconfig_declarations: "android.nfc.flags-aconfig",
-    min_sdk_version: "VanillaIceCream",
+    min_sdk_version: "34",
     apex_available: [
         "//apex_available:platform",
         "com.android.nfcservices",
@@ -224,12 +251,25 @@
     srcs: ["core/java/android/net/flags.aconfig"],
 }
 
+// Thread network
+aconfig_declarations {
+    name: "com.android.net.thread.flags-aconfig",
+    package: "com.android.net.thread.flags",
+    srcs: ["core/java/android/net/thread/flags.aconfig"],
+}
+
 java_aconfig_library {
     name: "com.android.net.flags-aconfig-java",
     aconfig_declarations: "com.android.net.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "com.android.net.thread.flags-aconfig-java",
+    aconfig_declarations: "com.android.net.thread.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media
 aconfig_declarations {
     name: "android.media.playback.flags-aconfig",
@@ -260,3 +300,16 @@
     aconfig_declarations: "android.net.vcn.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// CrashRecovery Module
+aconfig_declarations {
+    name: "android.crashrecovery.flags-aconfig",
+    package: "android.crashrecovery.flags",
+    srcs: ["packages/CrashRecovery/aconfig/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.crashrecovery.flags-aconfig-java",
+    aconfig_declarations: "android.crashrecovery.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 316555f..6f4593b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -127,6 +127,7 @@
         ":libcamera_client_aidl",
         ":libcamera_client_framework_aidl",
         ":libupdate_engine_aidl",
+        ":libupdate_engine_stable-V2-java-source",
         ":logd_aidl",
         ":resourcemanager_aidl",
         ":storaged_aidl",
@@ -426,7 +427,6 @@
     name: "framework-non-updatable-unbundled-impl-libs",
     static_libs: [
         "framework-location.impl",
-        "framework-nfc.impl",
     ],
     sdk_version: "core_platform",
     installable: false,
@@ -646,6 +646,7 @@
     lint: {
         baseline_filename: "lint-baseline.xml",
     },
+    apex_available: ["com.android.wifi"],
 }
 
 filegroup {
diff --git a/MEMORY_OWNERS b/MEMORY_OWNERS
new file mode 100644
index 0000000..89ce5140
--- /dev/null
+++ b/MEMORY_OWNERS
@@ -0,0 +1,6 @@
+surenb@google.com
+tjmercier@google.com
+kaleshsingh@google.com
+jyescas@google.com
+carlosgalo@google.com
+jji@google.com
diff --git a/OWNERS b/OWNERS
index 733157f..935b768 100644
--- a/OWNERS
+++ b/OWNERS
@@ -39,3 +39,5 @@
 per-file *Ravenwood* = file:ravenwood/OWNERS
 
 per-file PERFORMANCE_OWNERS = file:/PERFORMANCE_OWNERS
+
+per-file PACKAGE_MANAGER_OWNERS = file:/PACKAGE_MANAGER_OWNERS
\ No newline at end of file
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
index e4549b4..eb5842b 100644
--- a/PACKAGE_MANAGER_OWNERS
+++ b/PACKAGE_MANAGER_OWNERS
@@ -1,3 +1,3 @@
-chiuwinson@google.com
+alexbuy@google.com
 patb@google.com
 schfan@google.com
\ No newline at end of file
diff --git a/TEST_MAPPING b/TEST_MAPPING
index eef3d27..8338c33 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -125,6 +125,9 @@
     },
     {
       "name": "vts_treble_vintf_vendor_test"
+    },
+    {
+      "name": "CtsStrictJavaPackagesTestCases"
     }
   ],
   "postsubmit-managedprofile-stress": [
diff --git a/WEAR_OWNERS b/WEAR_OWNERS
index 4f3bc27..4127f99 100644
--- a/WEAR_OWNERS
+++ b/WEAR_OWNERS
@@ -4,3 +4,9 @@
 adsule@google.com
 andriyn@google.com
 yfz@google.com
+con@google.com
+leetodd@google.com
+sadrul@google.com
+rwmyers@google.com
+nalmalki@google.com
+shijianli@google.com
diff --git a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
index aadbc23..add0a08 100644
--- a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
+++ b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
@@ -16,8 +16,6 @@
 
 package android.input
 
-import android.content.Context
-import android.content.res.Resources
 import android.os.SystemProperties
 import android.perftests.utils.PerfStatusReporter
 import android.view.InputDevice
@@ -38,8 +36,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when`
 
 import java.time.Duration
 
@@ -68,18 +64,6 @@
                 InputDevice.SOURCE_STYLUS, /*flags=*/0)
 }
 
-private fun getPredictionContext(offset: Duration, enablePrediction: Boolean): Context {
-    val context = mock(Context::class.java)
-    val resources: Resources = mock(Resources::class.java)
-    `when`(context.getResources()).thenReturn(resources)
-    `when`(resources.getInteger(
-            com.android.internal.R.integer.config_motionPredictionOffsetNanos)).thenReturn(
-                offset.toNanos().toInt())
-    `when`(resources.getBoolean(
-            com.android.internal.R.bool.config_enableMotionPrediction)).thenReturn(enablePrediction)
-    return context
-}
-
 @RunWith(AndroidJUnit4::class)
 @LargeTest
 class MotionPredictorBenchmark {
@@ -115,7 +99,7 @@
         var eventPosition = 0f
         val positionInterval = 10f
 
-        val predictor = MotionPredictor(getPredictionContext(offset, /*enablePrediction=*/true))
+        val predictor = MotionPredictor(/*isPredictionEnabled=*/true, offset.toNanos().toInt())
         // ACTION_DOWN t=0 x=0 y=0
         predictor.record(getStylusMotionEvent(
             eventTime, ACTION_DOWN, /*x=*/eventPosition, /*y=*/eventPosition))
@@ -141,12 +125,11 @@
      */
     @Test
     fun timeCreatePredictor() {
-        val context = getPredictionContext(
-                /*offset=*/Duration.ofMillis(20), /*enablePrediction=*/true)
+        val offsetNanos = Duration.ofMillis(20).toNanos().toInt()
 
         val state = perfStatusReporter.getBenchmarkState()
         while (state.keepRunning()) {
-            MotionPredictor(context)
+            MotionPredictor(/*isPredictionEnabled=*/true, offsetNanos)
         }
     }
 }
diff --git a/api/Android.bp b/api/Android.bp
index 53988cd..1b84ce7 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -69,8 +69,40 @@
     visibility: ["//visibility:public"],
 }
 
+soong_config_module_type {
+    name: "enable_crashrecovery_module",
+    module_type: "combined_apis_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: ["release_crashrecovery_module"],
+    properties: [
+        "bootclasspath",
+        "system_server_classpath",
+    ],
+}
+
+soong_config_bool_variable {
+    name: "release_crashrecovery_module",
+}
+
+enable_crashrecovery_module {
+    name: "crashrecovery_module_defaults",
+    soong_config_variables: {
+        release_crashrecovery_module: {
+            bootclasspath: [
+                "framework-crashrecovery",
+            ],
+            system_server_classpath: [
+                "service-crashrecovery",
+            ],
+        },
+    },
+}
+
 combined_apis {
     name: "frameworks-base-api",
+    defaults: [
+        "crashrecovery_module_defaults",
+    ],
     bootclasspath: [
         "android.net.ipsec.ike",
         "art.module.public.api",
@@ -176,7 +208,7 @@
     out: ["current.srcjar"],
     cmd: "$(location merge_zips) $(out) $(in)",
     srcs: [
-        ":api-stubs-docs-non-updatable",
+        ":api-stubs-docs-non-updatable{.exportable}",
         ":all-modules-public-stubs-source",
     ],
     visibility: ["//visibility:private"], // Used by make module in //development, mind
@@ -199,7 +231,7 @@
     name: "sdk-annotations.zip",
     defaults: ["sdk-annotations-defaults"],
     srcs: [
-        ":android-non-updatable-doc-stubs{.annotations.zip}",
+        ":android-non-updatable-doc-stubs{.exportable.annotations.zip}",
         ":all-modules-public-annotations",
     ],
 }
@@ -208,7 +240,7 @@
     name: "sdk-annotations-system.zip",
     defaults: ["sdk-annotations-defaults"],
     srcs: [
-        ":android-non-updatable-doc-stubs-system{.annotations.zip}",
+        ":android-non-updatable-doc-stubs-system{.exportable.annotations.zip}",
         ":all-modules-system-annotations",
     ],
 }
@@ -217,7 +249,7 @@
     name: "sdk-annotations-module-lib.zip",
     defaults: ["sdk-annotations-defaults"],
     srcs: [
-        ":android-non-updatable-doc-stubs-module-lib{.annotations.zip}",
+        ":android-non-updatable-doc-stubs-module-lib{.exportable.annotations.zip}",
         ":all-modules-module-lib-annotations",
     ],
 }
@@ -226,7 +258,7 @@
     name: "sdk-annotations-system-server.zip",
     defaults: ["sdk-annotations-defaults"],
     srcs: [
-        ":android-non-updatable-doc-stubs-system-server{.annotations.zip}",
+        ":android-non-updatable-doc-stubs-system-server{.exportable.annotations.zip}",
         ":all-modules-system-server-annotations",
     ],
 }
@@ -278,6 +310,7 @@
 // classpath (or sources) somehow.
 stubs_defaults {
     name: "android-non-updatable-stubs-defaults",
+    defaults: ["framework-minus-apex-aconfig-declarations"],
     srcs: [":android-non-updatable-stub-sources"],
     sdk_version: "none",
     system_modules: "none",
@@ -458,3 +491,12 @@
         targets: ["droid"],
     },
 }
+
+phony_rule {
+    name: "checkapi",
+    phony_deps: [
+        "frameworks-base-api-current-compat",
+        "frameworks-base-api-system-current-compat",
+        "frameworks-base-api-module-lib-current-compat",
+    ],
+}
diff --git a/api/Android.mk b/api/Android.mk
deleted file mode 100644
index ce5f995..0000000
--- a/api/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-.PHONY: checkapi
-checkapi: frameworks-base-api-current-compat frameworks-base-api-system-current-compat frameworks-base-api-module-lib-current-compat
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 5744bdf..426ffdf 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -51,6 +51,7 @@
         ":framework-graphics-srcs",
         ":framework-mediaprovider-sources",
         ":framework-nearby-sources",
+        ":framework-nfc-updatable-sources",
         ":framework-ondevicepersonalization-sources",
         ":framework-permission-sources",
         ":framework-permission-s-sources",
@@ -171,6 +172,7 @@
         "-federationapi AndroidX $(location :current-androidx-api)",
         // doclava contains checks for a few issues that are have been migrated to metalava.
         // disable them in doclava, to avoid mistriggering or double triggering.
+        "-hide 101", // TODO: turn Lint 101 back into an error again
         "-hide 111", // HIDDEN_SUPERCLASS
         "-hide 113", // DEPRECATION_MISMATCH
         "-hide 125", // REQUIRES_PERMISSION
@@ -206,7 +208,7 @@
     name: "offline-sdk-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc offline",
@@ -227,7 +229,7 @@
     name: "offline-sdk-referenceonly-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc offline",
@@ -271,7 +273,7 @@
     name: "ds-docs-java",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc online",
@@ -305,7 +307,7 @@
 droiddoc {
     name: "ds-docs-kt",
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     flags: [
         "-noJdkLink",
@@ -359,7 +361,7 @@
     name: "ds-static-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc online",
@@ -376,7 +378,7 @@
     name: "ds-ref-navtree-docs",
     defaults: ["framework-docs-default"],
     srcs: [
-        ":framework-doc-stubs",
+        ":framework-doc-stubs{.exportable}",
     ],
     hdf: [
         "android.whichdoc online",
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 28b2d4b..852abdf 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -239,6 +239,10 @@
     name: "android-non-updatable_from_source_defaults",
     libs: ["stub-annotations"],
     static_libs: ["framework-res-package-jar"], // Export package of framework-res
+}
+
+java_defaults {
+    name: "android-non-updatable_exportable_from_source_defaults",
     dist: {
         targets: ["sdk"],
         tag: ".jar",
@@ -265,6 +269,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.system",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -283,6 +295,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.system",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.system.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.module_lib",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -301,6 +321,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.module_lib",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.module_lib.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.test",
     defaults: ["android-non-updatable_defaults"],
     static_libs: [
@@ -319,6 +347,14 @@
 }
 
 java_library {
+    name: "android-non-updatable.stubs.exportable.test",
+    defaults: ["android-non-updatable_defaults"],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.test.from-source",
+    ],
+}
+
+java_library {
     name: "android-non-updatable.stubs.from-source",
     defaults: [
         "android-non-updatable_defaults",
@@ -326,6 +362,17 @@
     ],
     srcs: [":api-stubs-docs-non-updatable"],
     libs: ["all-modules-public-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-public-stubs"],
     dist: {
         dir: "apistubs/android/public",
     },
@@ -339,6 +386,17 @@
     ],
     srcs: [":system-api-stubs-docs-non-updatable"],
     libs: ["all-modules-system-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.system.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":system-api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-system-stubs"],
     dist: {
         dir: "apistubs/android/system",
     },
@@ -352,6 +410,17 @@
     ],
     srcs: [":module-lib-api-stubs-docs-non-updatable"],
     libs: non_updatable_api_deps_on_modules,
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.module_lib.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable}"],
+    libs: non_updatable_api_deps_on_modules,
     dist: {
         dir: "apistubs/android/module-lib",
     },
@@ -365,6 +434,17 @@
     ],
     srcs: [":test-api-stubs-docs-non-updatable"],
     libs: ["all-modules-system-stubs"],
+}
+
+java_library {
+    name: "android-non-updatable.stubs.exportable.test.from-source",
+    defaults: [
+        "android-non-updatable_defaults",
+        "android-non-updatable_from_source_defaults",
+        "android-non-updatable_exportable_from_source_defaults",
+    ],
+    srcs: [":test-api-stubs-docs-non-updatable{.exportable}"],
+    libs: ["all-modules-system-stubs"],
     dist: {
         dir: "apistubs/android/test",
     },
@@ -462,6 +542,16 @@
 }
 
 java_library {
+    name: "android_stubs_current_exportable.from-source",
+    static_libs: [
+        "all-modules-public-stubs-exportable",
+        "android-non-updatable.stubs.exportable",
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["android.jar_defaults"],
+}
+
+java_library {
     name: "android_system_stubs_current.from-source",
     static_libs: [
         "all-modules-system-stubs",
@@ -470,6 +560,19 @@
     ],
     defaults: [
         "android.jar_defaults",
+    ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_system_stubs_current_exportable.from-source",
+    static_libs: [
+        "all-modules-system-stubs-exportable",
+        "android-non-updatable.stubs.exportable.system",
+        "private-stub-annotations-jar",
+    ],
+    defaults: [
+        "android.jar_defaults",
         "android_stubs_dists_default",
     ],
     dist: {
@@ -498,6 +601,23 @@
     ],
     defaults: [
         "android.jar_defaults",
+    ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_test_stubs_current_exportable.from-source",
+    static_libs: [
+        // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
+        // include the SystemApi of framework-non-updatable-sources.
+        "all-updatable-modules-system-stubs-exportable",
+        // Non-updatable modules on the other hand can have test APIs, so include their test-stubs.
+        "all-non-updatable-modules-test-stubs-exportable",
+        "android-non-updatable.stubs.exportable.test",
+        "private-stub-annotations-jar",
+    ],
+    defaults: [
+        "android.jar_defaults",
         "android_stubs_dists_default",
     ],
     dist: {
@@ -505,6 +625,7 @@
     },
 }
 
+// This module does not need to be copied to dist
 java_library {
     name: "android_test_frameworks_core_stubs_current.from-source",
     static_libs: [
@@ -513,24 +634,34 @@
     ],
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
-    dist: {
-        dir: "apistubs/android/test-core",
-    },
+    visibility: ["//frameworks/base/services"],
 }
 
 java_library {
     name: "android_module_lib_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
     static_libs: [
         "android-non-updatable.stubs.module_lib",
         "art.module.public.api.stubs.module_lib",
         "i18n.module.public.api.stubs",
     ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_module_lib_stubs_current_exportable.from-source",
+    defaults: [
+        "android.jar_defaults",
+        "android_stubs_dists_default",
+    ],
+    static_libs: [
+        "android-non-updatable.stubs.exportable.module_lib",
+        "art.module.public.api.stubs.exportable.module_lib",
+        "i18n.module.public.api.stubs.exportable",
+    ],
     dist: {
         dir: "apistubs/android/module-lib",
     },
@@ -540,13 +671,26 @@
     name: "android_system_server_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
-        "android_stubs_dists_default",
     ],
     srcs: [":services-non-updatable-stubs"],
     installable: false,
     static_libs: [
         "android_module_lib_stubs_current.from-source",
     ],
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library {
+    name: "android_system_server_stubs_current_exportable.from-source",
+    defaults: [
+        "android.jar_defaults",
+        "android_stubs_dists_default",
+    ],
+    srcs: [":services-non-updatable-stubs{.exportable}"],
+    installable: false,
+    static_libs: [
+        "android_module_lib_stubs_current_exportable.from-source",
+    ],
     dist: {
         dir: "apistubs/android/system-server",
     },
@@ -635,7 +779,6 @@
     api_contributions: [
         "framework-virtualization.stubs.source.test.api.contribution",
         "framework-location.stubs.source.test.api.contribution",
-        "framework-nfc.stubs.source.test.api.contribution",
     ],
 }
 
@@ -818,7 +961,7 @@
 java_library {
     name: "android_stubs_current_with_test_libs",
     static_libs: [
-        "android_stubs_current",
+        "android_stubs_current_exportable",
         "android.test.base.stubs",
         "android.test.mock.stubs",
         "android.test.runner.stubs",
@@ -833,7 +976,7 @@
 java_library {
     name: "android_system_stubs_current_with_test_libs",
     static_libs: [
-        "android_system_stubs_current",
+        "android_system_stubs_current_exportable",
         "android.test.base.stubs.system",
         "android.test.mock.stubs.system",
         "android.test.runner.stubs.system",
@@ -848,7 +991,7 @@
 java_library {
     name: "android_module_stubs_current_with_test_libs",
     static_libs: [
-        "android_module_lib_stubs_current",
+        "android_module_lib_stubs_current_exportable",
         "android.test.base.stubs",
         "android.test.mock.stubs",
         "android.test.runner.stubs",
@@ -863,7 +1006,7 @@
 java_library {
     name: "android_system_server_stubs_current_with_test_libs",
     static_libs: [
-        "android_system_server_stubs_current",
+        "android_system_server_stubs_current_exportable",
         "android.test.base.stubs.system",
         "android.test.mock.stubs.system",
         "android.test.runner.stubs.system",
@@ -900,10 +1043,19 @@
     ],
     api_levels_sdk_type: "system",
     extensions_info_file: ":sdk-extensions-info",
+    dists: [
+        // Make the api-versions.xml file for the system API available in the
+        // sdk build target.
+        {
+            targets: ["sdk"],
+            dest: "api-versions_system.xml",
+            tag: ".api_versions.xml",
+        },
+    ],
 }
 
 // This module can be built with:
-// m out/soong/.intermediates/frameworks/base/api_versions_module_lib/android_common/metalava/api-versions.xml
+// m out/soong/.intermediates/frameworks/base/api/api_versions_module_lib/android_common/metalava/api-versions.xml
 droidstubs {
     name: "api_versions_module_lib",
     srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
diff --git a/api/api.go b/api/api.go
index 2668999..fa2be21 100644
--- a/api/api.go
+++ b/api/api.go
@@ -31,7 +31,6 @@
 const i18n = "i18n.module.public.api"
 const virtualization = "framework-virtualization"
 const location = "framework-location"
-const nfc = "framework-nfc"
 
 var core_libraries_modules = []string{art, conscrypt, i18n}
 
@@ -43,7 +42,7 @@
 // APIs.
 // In addition, the modules in this list are allowed to contribute to test APIs
 // stubs.
-var non_updatable_modules = []string{virtualization, location, nfc}
+var non_updatable_modules = []string{virtualization, location}
 
 // The intention behind this soong plugin is to generate a number of "merged"
 // API-related modules that would otherwise require a large amount of very
@@ -64,6 +63,7 @@
 
 type CombinedApis struct {
 	android.ModuleBase
+	android.DefaultableModuleBase
 
 	properties CombinedApisProperties
 }
@@ -74,6 +74,7 @@
 
 func registerBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("combined_apis", combinedApisModuleFactory)
+	ctx.RegisterModuleType("combined_apis_defaults", CombinedApisModuleDefaultsFactory)
 }
 
 var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents)
@@ -203,6 +204,15 @@
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
+func createMergedPublicExportableStubs(ctx android.LoadHookContext, modules []string) {
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("all-modules-public-stubs-exportable")
+	props.Static_libs = transformArray(modules, "", ".stubs.exportable")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
 	// First create the all-updatable-modules-system-stubs
 	{
@@ -227,6 +237,30 @@
 	}
 }
 
+func createMergedSystemExportableStubs(ctx android.LoadHookContext, modules []string) {
+	// First create the all-updatable-modules-system-stubs
+	{
+		updatable_modules := removeAll(modules, non_updatable_modules)
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-updatable-modules-system-stubs-exportable")
+		props.Static_libs = transformArray(updatable_modules, "", ".stubs.exportable.system")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+	// Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules
+	// into all-modules-system-stubs.
+	{
+		props := libraryProps{}
+		props.Name = proptools.StringPtr("all-modules-system-stubs-exportable")
+		props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.system")
+		props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs-exportable")
+		props.Sdk_version = proptools.StringPtr("module_current")
+		props.Visibility = []string{"//frameworks/base"}
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+}
+
 func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) {
 	props := libraryProps{}
 	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs")
@@ -236,6 +270,15 @@
 	ctx.CreateModule(java.LibraryFactory, &props)
 }
 
+func createMergedTestExportableStubsForNonUpdatableModules(ctx android.LoadHookContext) {
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs-exportable")
+	props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.exportable.test")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
 	// This module is for the "framework-all" module, which should not include the core libraries.
 	modules = removeAll(modules, core_libraries_modules)
@@ -266,6 +309,19 @@
 	}
 }
 
+func createMergedFrameworkModuleLibExportableStubs(ctx android.LoadHookContext, modules []string) {
+	// The user of this module compiles against the "core" SDK and against non-updatable modules,
+	// so remove to avoid dupes.
+	modules = removeAll(modules, core_libraries_modules)
+	modules = removeAll(modules, non_updatable_modules)
+	props := libraryProps{}
+	props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api-exportable")
+	props.Static_libs = transformArray(modules, "", ".stubs.exportable.module_lib")
+	props.Sdk_version = proptools.StringPtr("module_current")
+	props.Visibility = []string{"//frameworks/base"}
+	ctx.CreateModule(java.LibraryFactory, &props)
+}
+
 func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
 	// The user of this module compiles against the "core" SDK and against non-updatable modules,
 	// so remove to avoid dupes.
@@ -381,6 +437,27 @@
 	}
 }
 
+func createFullExportableApiLibraries(ctx android.LoadHookContext) {
+	javaLibraryNames := []string{
+		"android_stubs_current_exportable",
+		"android_system_stubs_current_exportable",
+		"android_test_stubs_current_exportable",
+		"android_module_lib_stubs_current_exportable",
+		"android_system_server_stubs_current_exportable",
+	}
+
+	for _, libraryName := range javaLibraryNames {
+		props := libraryProps{}
+		props.Name = proptools.StringPtr(libraryName)
+		staticLib := libraryName + ".from-source"
+		props.Static_libs = []string{staticLib}
+		props.Defaults = []string{"android.jar_defaults"}
+		props.Visibility = []string{"//visibility:public"}
+
+		ctx.CreateModule(java.LibraryFactory, &props)
+	}
+}
+
 func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
 	bootclasspath := a.properties.Bootclasspath
 	system_server_classpath := a.properties.System_server_classpath
@@ -396,6 +473,11 @@
 	createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
 	createMergedFrameworkImpl(ctx, bootclasspath)
 
+	createMergedPublicExportableStubs(ctx, bootclasspath)
+	createMergedSystemExportableStubs(ctx, bootclasspath)
+	createMergedTestExportableStubsForNonUpdatableModules(ctx)
+	createMergedFrameworkModuleLibExportableStubs(ctx, bootclasspath)
+
 	createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath)
 
 	createPublicStubsSourceFilegroup(ctx, bootclasspath)
@@ -403,12 +485,15 @@
 	createApiContributionDefaults(ctx, bootclasspath)
 
 	createFullApiLibraries(ctx)
+
+	createFullExportableApiLibraries(ctx)
 }
 
 func combinedApisModuleFactory() android.Module {
 	module := &CombinedApis{}
 	module.AddProperties(&module.properties)
 	android.InitAndroidModule(module)
+	android.InitDefaultableModule(module)
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
 	return module
 }
@@ -445,3 +530,16 @@
 	}
 	return s2
 }
+
+// Defaults
+type CombinedApisModuleDefaults struct {
+	android.ModuleBase
+	android.DefaultsModuleBase
+}
+
+func CombinedApisModuleDefaultsFactory() android.Module {
+	module := &CombinedApisModuleDefaults{}
+	module.AddProperties(&CombinedApisProperties{})
+	android.InitDefaultsModule(module)
+	return module
+}
diff --git a/boot/Android.bp b/boot/Android.bp
index 8a3d35e..4b3ad655 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -29,6 +29,7 @@
     config_namespace: "AUTO",
     bool_variables: [
         "car_bootclasspath_fragment",
+        "release_crashrecovery_module",
     ],
     properties: [
         "fragments",
@@ -155,6 +156,15 @@
                 },
             ],
         },
+        release_crashrecovery_module: {
+            fragments: [
+                // only used when crashrecovery is enabled
+                {
+                    apex: "com.android.crashrecovery",
+                    module: "com.android.crashrecovery-bootclasspath-fragment",
+                },
+            ],
+        },
     },
 
     // Additional information needed by hidden api processing.
diff --git a/config/preloaded-classes b/config/preloaded-classes
index fd4e3df..0a77877 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6265,8 +6265,6 @@
 android.os.VibratorInfo$FrequencyProfile
 android.os.VibratorInfo
 android.os.VibratorManager
-android.os.VintfObject
-android.os.VintfRuntimeInfo
 android.os.WorkSource$1
 android.os.WorkSource$WorkChain$1
 android.os.WorkSource$WorkChain
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 907916a..8d8a82b 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -96,21 +96,3 @@
     name: "non-updatable-test-lint-baseline.txt",
     srcs: ["test-lint-baseline.txt"],
 }
-
-java_api_contribution {
-    name: "api-stubs-docs-non-updatable-public-stubs",
-    api_surface: "public",
-    api_file: "current.txt",
-    visibility: [
-        "//build/orchestrator/apis",
-    ],
-}
-
-java_api_contribution {
-    name: "frameworks-base-core-api-module-lib-stubs",
-    api_surface: "module-lib",
-    api_file: "module-lib-current.txt",
-    visibility: [
-        "//build/orchestrator/apis",
-    ],
-}
diff --git a/core/api/current.txt b/core/api/current.txt
index c600df1..ea1cd21d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -193,6 +193,7 @@
     field public static final String MANAGE_DEVICE_POLICY_SYSTEM_APPS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_APPS";
     field public static final String MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS";
     field public static final String MANAGE_DEVICE_POLICY_SYSTEM_UPDATES = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES";
+    field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_THREAD_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK";
     field public static final String MANAGE_DEVICE_POLICY_TIME = "android.permission.MANAGE_DEVICE_POLICY_TIME";
     field public static final String MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING = "android.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING";
     field public static final String MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER = "android.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER";
@@ -16384,7 +16385,6 @@
   }
 
   public final class RecordingCanvas extends android.graphics.Canvas {
-    method public final void drawMesh(@NonNull android.graphics.Mesh, android.graphics.BlendMode, @NonNull android.graphics.Paint);
   }
 
   public final class Rect implements android.os.Parcelable {
@@ -22573,6 +22573,7 @@
     method @NonNull public java.util.List<java.lang.String> getSupportedVendorParameters();
     method @Nullable public static android.media.Image mapHardwareBuffer(@NonNull android.hardware.HardwareBuffer);
     method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
+    method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void queueInputBuffers(int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>);
     method public void queueSecureInputBuffer(int, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException;
     method public void release();
     method public void releaseOutputBuffer(int, boolean);
@@ -22634,6 +22635,7 @@
     method public abstract void onError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CodecException);
     method public abstract void onInputBufferAvailable(@NonNull android.media.MediaCodec, int);
     method public abstract void onOutputBufferAvailable(@NonNull android.media.MediaCodec, int, @NonNull android.media.MediaCodec.BufferInfo);
+    method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void onOutputBuffersAvailable(@NonNull android.media.MediaCodec, int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>);
     method public abstract void onOutputFormatChanged(@NonNull android.media.MediaCodec, @NonNull android.media.MediaFormat);
   }
 
@@ -22721,6 +22723,7 @@
   }
 
   public static final class MediaCodec.OutputFrame {
+    method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public java.util.ArrayDeque<android.media.MediaCodec.BufferInfo> getBufferInfos();
     method @NonNull public java.util.Set<java.lang.String> getChangedKeys();
     method public int getFlags();
     method @NonNull public android.media.MediaFormat getFormat();
@@ -22736,6 +22739,7 @@
 
   public final class MediaCodec.QueueRequest {
     method public void queue();
+    method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public android.media.MediaCodec.QueueRequest setBufferInfos(@NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>);
     method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer);
     method @NonNull public android.media.MediaCodec.QueueRequest setEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, @NonNull android.media.MediaCodec.CryptoInfo);
     method @NonNull public android.media.MediaCodec.QueueRequest setFlags(int);
@@ -23603,6 +23607,8 @@
     field public static final String KEY_AUDIO_SESSION_ID = "audio-session-id";
     field public static final String KEY_BITRATE_MODE = "bitrate-mode";
     field public static final String KEY_BIT_RATE = "bitrate";
+    field @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public static final String KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE = "buffer-batch-max-output-size";
+    field @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public static final String KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE = "buffer-batch-threshold-output-size";
     field public static final String KEY_CAPTION_SERVICE_NUMBER = "caption-service-number";
     field public static final String KEY_CAPTURE_RATE = "capture-rate";
     field public static final String KEY_CHANNEL_COUNT = "channel-count";
@@ -23631,6 +23637,7 @@
     field public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
     field public static final String KEY_HDR_STATIC_INFO = "hdr-static-info";
     field public static final String KEY_HEIGHT = "height";
+    field @FlaggedApi("com.android.media.codec.flags.codec_importance") public static final String KEY_IMPORTANCE = "importance";
     field public static final String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
     field public static final String KEY_IS_ADTS = "is-adts";
     field public static final String KEY_IS_AUTOSELECT = "is-autoselect";
@@ -29040,435 +29047,6 @@
 
 }
 
-package android.nfc {
-
-  public final class AvailableNfcAntenna implements android.os.Parcelable {
-    ctor public AvailableNfcAntenna(int, int);
-    method public int describeContents();
-    method public int getLocationX();
-    method public int getLocationY();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.AvailableNfcAntenna> CREATOR;
-  }
-
-  public class FormatException extends java.lang.Exception {
-    ctor public FormatException();
-    ctor public FormatException(String);
-    ctor public FormatException(String, Throwable);
-  }
-
-  public final class NdefMessage implements android.os.Parcelable {
-    ctor public NdefMessage(byte[]) throws android.nfc.FormatException;
-    ctor public NdefMessage(android.nfc.NdefRecord, android.nfc.NdefRecord...);
-    ctor public NdefMessage(android.nfc.NdefRecord[]);
-    method public int describeContents();
-    method public int getByteArrayLength();
-    method public android.nfc.NdefRecord[] getRecords();
-    method public byte[] toByteArray();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefMessage> CREATOR;
-  }
-
-  public final class NdefRecord implements android.os.Parcelable {
-    ctor public NdefRecord(short, byte[], byte[], byte[]);
-    ctor @Deprecated public NdefRecord(byte[]) throws android.nfc.FormatException;
-    method public static android.nfc.NdefRecord createApplicationRecord(String);
-    method public static android.nfc.NdefRecord createExternal(String, String, byte[]);
-    method public static android.nfc.NdefRecord createMime(String, byte[]);
-    method public static android.nfc.NdefRecord createTextRecord(String, String);
-    method public static android.nfc.NdefRecord createUri(android.net.Uri);
-    method public static android.nfc.NdefRecord createUri(String);
-    method public int describeContents();
-    method public byte[] getId();
-    method public byte[] getPayload();
-    method public short getTnf();
-    method public byte[] getType();
-    method @Deprecated public byte[] toByteArray();
-    method public String toMimeType();
-    method public android.net.Uri toUri();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefRecord> CREATOR;
-    field public static final byte[] RTD_ALTERNATIVE_CARRIER;
-    field public static final byte[] RTD_HANDOVER_CARRIER;
-    field public static final byte[] RTD_HANDOVER_REQUEST;
-    field public static final byte[] RTD_HANDOVER_SELECT;
-    field public static final byte[] RTD_SMART_POSTER;
-    field public static final byte[] RTD_TEXT;
-    field public static final byte[] RTD_URI;
-    field public static final short TNF_ABSOLUTE_URI = 3; // 0x3
-    field public static final short TNF_EMPTY = 0; // 0x0
-    field public static final short TNF_EXTERNAL_TYPE = 4; // 0x4
-    field public static final short TNF_MIME_MEDIA = 2; // 0x2
-    field public static final short TNF_UNCHANGED = 6; // 0x6
-    field public static final short TNF_UNKNOWN = 5; // 0x5
-    field public static final short TNF_WELL_KNOWN = 1; // 0x1
-  }
-
-  public final class NfcAdapter {
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
-    method public void disableForegroundDispatch(android.app.Activity);
-    method public void disableReaderMode(android.app.Activity);
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
-    method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
-    method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
-    method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
-    method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
-    method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
-    method public boolean isEnabled();
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
-    method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
-    method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
-    method public boolean isSecureNfcEnabled();
-    method public boolean isSecureNfcSupported();
-    field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
-    field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
-    field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
-    field public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
-    field public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
-    field @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT) public static final String ACTION_TRANSACTION_DETECTED = "android.nfc.action.TRANSACTION_DETECTED";
-    field public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
-    field public static final String EXTRA_AID = "android.nfc.extra.AID";
-    field public static final String EXTRA_DATA = "android.nfc.extra.DATA";
-    field public static final String EXTRA_ID = "android.nfc.extra.ID";
-    field public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
-    field public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON = "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
-    field public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
-    field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
-    field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
-    field public static final int FLAG_READER_NFC_A = 1; // 0x1
-    field public static final int FLAG_READER_NFC_B = 2; // 0x2
-    field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
-    field public static final int FLAG_READER_NFC_F = 4; // 0x4
-    field public static final int FLAG_READER_NFC_V = 8; // 0x8
-    field public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 256; // 0x100
-    field public static final int FLAG_READER_SKIP_NDEF_CHECK = 128; // 0x80
-    field public static final int PREFERRED_PAYMENT_CHANGED = 2; // 0x2
-    field public static final int PREFERRED_PAYMENT_LOADED = 1; // 0x1
-    field public static final int PREFERRED_PAYMENT_UPDATED = 3; // 0x3
-    field public static final int STATE_OFF = 1; // 0x1
-    field public static final int STATE_ON = 3; // 0x3
-    field public static final int STATE_TURNING_OFF = 4; // 0x4
-    field public static final int STATE_TURNING_ON = 2; // 0x2
-  }
-
-  @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
-    method @Deprecated public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
-  }
-
-  @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
-    method @Deprecated public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
-  }
-
-  @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
-    method @Deprecated public void onNdefPushComplete(android.nfc.NfcEvent);
-  }
-
-  public static interface NfcAdapter.OnTagRemovedListener {
-    method public void onTagRemoved();
-  }
-
-  public static interface NfcAdapter.ReaderCallback {
-    method public void onTagDiscovered(android.nfc.Tag);
-  }
-
-  public final class NfcAntennaInfo implements android.os.Parcelable {
-    ctor public NfcAntennaInfo(int, int, boolean, @NonNull java.util.List<android.nfc.AvailableNfcAntenna>);
-    method public int describeContents();
-    method @NonNull public java.util.List<android.nfc.AvailableNfcAntenna> getAvailableNfcAntennas();
-    method public int getDeviceHeight();
-    method public int getDeviceWidth();
-    method public boolean isDeviceFoldable();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NfcAntennaInfo> CREATOR;
-  }
-
-  public final class NfcEvent {
-    field public final android.nfc.NfcAdapter nfcAdapter;
-    field public final int peerLlcpMajorVersion;
-    field public final int peerLlcpMinorVersion;
-  }
-
-  public final class NfcManager {
-    method public android.nfc.NfcAdapter getDefaultAdapter();
-  }
-
-  public final class Tag implements android.os.Parcelable {
-    method public int describeContents();
-    method public byte[] getId();
-    method public String[] getTechList();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.Tag> CREATOR;
-  }
-
-  public class TagLostException extends java.io.IOException {
-    ctor public TagLostException();
-    ctor public TagLostException(String);
-  }
-
-}
-
-package android.nfc.cardemulation {
-
-  public final class CardEmulation {
-    method public boolean categoryAllowsForegroundPreference(String);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
-    method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
-    method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
-    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
-    method public int getSelectionModeForCategory(String);
-    method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
-    method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
-    method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
-    method public boolean removeAidsForService(android.content.ComponentName, String);
-    method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
-    method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
-    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean);
-    method public boolean supportsAidPrefixRegistration();
-    method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
-    method public boolean unsetPreferredService(android.app.Activity);
-    field public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
-    field public static final String CATEGORY_OTHER = "other";
-    field public static final String CATEGORY_PAYMENT = "payment";
-    field public static final String EXTRA_CATEGORY = "category";
-    field public static final String EXTRA_SERVICE_COMPONENT = "component";
-    field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
-    field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
-    field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
-  }
-
-  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 public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
-    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
-    method public final void sendResponseApdu(byte[]);
-    field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
-    field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
-    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
-    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
-  }
-
-  public abstract class HostNfcFService extends android.app.Service {
-    ctor public HostNfcFService();
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method public abstract void onDeactivated(int);
-    method public abstract byte[] processNfcFPacket(byte[], android.os.Bundle);
-    method public final void sendResponsePacket(byte[]);
-    field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
-    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_NFCF_SERVICE";
-    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_nfcf_service";
-  }
-
-  public final class NfcFCardEmulation {
-    method public boolean disableService(android.app.Activity) throws java.lang.RuntimeException;
-    method public boolean enableService(android.app.Activity, android.content.ComponentName) throws java.lang.RuntimeException;
-    method public static android.nfc.cardemulation.NfcFCardEmulation getInstance(android.nfc.NfcAdapter);
-    method public String getNfcid2ForService(android.content.ComponentName) throws java.lang.RuntimeException;
-    method public String getSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
-    method public boolean registerSystemCodeForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
-    method public boolean setNfcid2ForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
-    method public boolean unregisterSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
-  }
-
-  public abstract class OffHostApduService extends android.app.Service {
-    ctor public OffHostApduService();
-    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
-    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
-  }
-
-}
-
-package android.nfc.tech {
-
-  public final class IsoDep implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.IsoDep get(android.nfc.Tag);
-    method public byte[] getHiLayerResponse();
-    method public byte[] getHistoricalBytes();
-    method public int getMaxTransceiveLength();
-    method public android.nfc.Tag getTag();
-    method public int getTimeout();
-    method public boolean isConnected();
-    method public boolean isExtendedLengthApduSupported();
-    method public void setTimeout(int);
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-  }
-
-  public final class MifareClassic implements android.nfc.tech.TagTechnology {
-    method public boolean authenticateSectorWithKeyA(int, byte[]) throws java.io.IOException;
-    method public boolean authenticateSectorWithKeyB(int, byte[]) throws java.io.IOException;
-    method public int blockToSector(int);
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public void decrement(int, int) throws java.io.IOException;
-    method public static android.nfc.tech.MifareClassic get(android.nfc.Tag);
-    method public int getBlockCount();
-    method public int getBlockCountInSector(int);
-    method public int getMaxTransceiveLength();
-    method public int getSectorCount();
-    method public int getSize();
-    method public android.nfc.Tag getTag();
-    method public int getTimeout();
-    method public int getType();
-    method public void increment(int, int) throws java.io.IOException;
-    method public boolean isConnected();
-    method public byte[] readBlock(int) throws java.io.IOException;
-    method public void restore(int) throws java.io.IOException;
-    method public int sectorToBlock(int);
-    method public void setTimeout(int);
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-    method public void transfer(int) throws java.io.IOException;
-    method public void writeBlock(int, byte[]) throws java.io.IOException;
-    field public static final int BLOCK_SIZE = 16; // 0x10
-    field public static final byte[] KEY_DEFAULT;
-    field public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY;
-    field public static final byte[] KEY_NFC_FORUM;
-    field public static final int SIZE_1K = 1024; // 0x400
-    field public static final int SIZE_2K = 2048; // 0x800
-    field public static final int SIZE_4K = 4096; // 0x1000
-    field public static final int SIZE_MINI = 320; // 0x140
-    field public static final int TYPE_CLASSIC = 0; // 0x0
-    field public static final int TYPE_PLUS = 1; // 0x1
-    field public static final int TYPE_PRO = 2; // 0x2
-    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
-  }
-
-  public final class MifareUltralight implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.MifareUltralight get(android.nfc.Tag);
-    method public int getMaxTransceiveLength();
-    method public android.nfc.Tag getTag();
-    method public int getTimeout();
-    method public int getType();
-    method public boolean isConnected();
-    method public byte[] readPages(int) throws java.io.IOException;
-    method public void setTimeout(int);
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-    method public void writePage(int, byte[]) throws java.io.IOException;
-    field public static final int PAGE_SIZE = 4; // 0x4
-    field public static final int TYPE_ULTRALIGHT = 1; // 0x1
-    field public static final int TYPE_ULTRALIGHT_C = 2; // 0x2
-    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
-  }
-
-  public final class Ndef implements android.nfc.tech.TagTechnology {
-    method public boolean canMakeReadOnly();
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.Ndef get(android.nfc.Tag);
-    method public android.nfc.NdefMessage getCachedNdefMessage();
-    method public int getMaxSize();
-    method public android.nfc.NdefMessage getNdefMessage() throws android.nfc.FormatException, java.io.IOException;
-    method public android.nfc.Tag getTag();
-    method public String getType();
-    method public boolean isConnected();
-    method public boolean isWritable();
-    method public boolean makeReadOnly() throws java.io.IOException;
-    method public void writeNdefMessage(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
-    field public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
-    field public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
-    field public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
-    field public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
-    field public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
-  }
-
-  public final class NdefFormatable implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public void format(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
-    method public void formatReadOnly(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
-    method public static android.nfc.tech.NdefFormatable get(android.nfc.Tag);
-    method public android.nfc.Tag getTag();
-    method public boolean isConnected();
-  }
-
-  public final class NfcA implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.NfcA get(android.nfc.Tag);
-    method public byte[] getAtqa();
-    method public int getMaxTransceiveLength();
-    method public short getSak();
-    method public android.nfc.Tag getTag();
-    method public int getTimeout();
-    method public boolean isConnected();
-    method public void setTimeout(int);
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-  }
-
-  public final class NfcB implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.NfcB get(android.nfc.Tag);
-    method public byte[] getApplicationData();
-    method public int getMaxTransceiveLength();
-    method public byte[] getProtocolInfo();
-    method public android.nfc.Tag getTag();
-    method public boolean isConnected();
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-  }
-
-  public final class NfcBarcode implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.NfcBarcode get(android.nfc.Tag);
-    method public byte[] getBarcode();
-    method public android.nfc.Tag getTag();
-    method public int getType();
-    method public boolean isConnected();
-    field public static final int TYPE_KOVIO = 1; // 0x1
-    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
-  }
-
-  public final class NfcF implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.NfcF get(android.nfc.Tag);
-    method public byte[] getManufacturer();
-    method public int getMaxTransceiveLength();
-    method public byte[] getSystemCode();
-    method public android.nfc.Tag getTag();
-    method public int getTimeout();
-    method public boolean isConnected();
-    method public void setTimeout(int);
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-  }
-
-  public final class NfcV implements android.nfc.tech.TagTechnology {
-    method public void close() throws java.io.IOException;
-    method public void connect() throws java.io.IOException;
-    method public static android.nfc.tech.NfcV get(android.nfc.Tag);
-    method public byte getDsfId();
-    method public int getMaxTransceiveLength();
-    method public byte getResponseFlags();
-    method public android.nfc.Tag getTag();
-    method public boolean isConnected();
-    method public byte[] transceive(byte[]) throws java.io.IOException;
-  }
-
-  public interface TagTechnology extends java.io.Closeable {
-    method public void connect() throws java.io.IOException;
-    method public android.nfc.Tag getTag();
-    method public boolean isConnected();
-  }
-
-}
-
 package android.opengl {
 
   public class EGL14 {
@@ -34020,6 +33598,7 @@
     field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
     field public static final String DISALLOW_SMS = "no_sms";
     field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+    field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
     field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
     field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
     field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 91c082a..76bc016 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -356,11 +356,6 @@
 
 package android.nfc {
 
-  public class NfcFrameworkInitializer {
-    method public static void registerServiceWrappers();
-    method public static void setNfcServiceManager(@NonNull android.nfc.NfcServiceManager);
-  }
-
   public class NfcServiceManager {
     method @NonNull public android.nfc.NfcServiceManager.ServiceRegisterer getNfcManagerServiceRegisterer();
   }
@@ -381,7 +376,9 @@
 package android.os {
 
   public class ArtModuleServiceManager {
+    method @FlaggedApi("android.content.pm.use_art_service_v2") @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdPreRebootServiceRegisterer();
     method @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdServiceRegisterer();
+    method @FlaggedApi("android.content.pm.use_art_service_v2") @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getDexoptChrootSetupServiceRegisterer();
   }
 
   public static final class ArtModuleServiceManager.ServiceRegisterer {
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 8b3696a..1fa1e89 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -252,22 +252,6 @@
 
 }
 
-package android.nfc {
-
-  public final class NfcAdapter {
-    method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
-    method @Deprecated public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
-    method @Deprecated public boolean invokeBeam(android.app.Activity);
-    method @Deprecated public boolean isNdefPushEnabled();
-    method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
-    method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
-    method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
-    method @Deprecated public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
-    method @Deprecated public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
-  }
-
-}
-
 package android.os {
 
   public class BatteryManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4c2e4fc..cdeddfb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3860,6 +3860,7 @@
     method public void setInstallAsInstantApp(boolean);
     method public void setInstallAsVirtualPreload();
     method public void setRequestDowngrade(boolean);
+    method @FlaggedApi("android.content.pm.rollback_lifetime") @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void setRollbackLifetimeMillis(long);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
   }
 
@@ -10236,42 +10237,6 @@
 
 }
 
-package android.nfc {
-
-  public final class NfcAdapter {
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
-    method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
-    method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
-    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
-    method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
-    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
-    field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
-    field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
-    field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
-    field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
-  }
-
-  public static interface NfcAdapter.ControllerAlwaysOnListener {
-    method public void onControllerAlwaysOnChanged(boolean);
-  }
-
-  public static interface NfcAdapter.NfcUnlockHandler {
-    method public boolean onUnlockAttempted(android.nfc.Tag);
-  }
-
-}
-
 package android.nfc.cardemulation {
 
   @FlaggedApi("android.nfc.enable_nfc_mainline") public final class AidGroup implements android.os.Parcelable {
@@ -10320,10 +10285,6 @@
     field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
   }
 
-  public final class CardEmulation {
-    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
-  }
-
   @FlaggedApi("android.nfc.enable_nfc_mainline") public final class NfcFServiceInfo implements android.os.Parcelable {
     ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public NfcFServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
@@ -10925,7 +10886,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static int rebootAndApply(@NonNull android.content.Context, @NonNull String, boolean) throws java.io.IOException;
     method @RequiresPermission(allOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootWipeAb(android.content.Context, java.io.File, String) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
-    method public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
+    method @Deprecated public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
     field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_INVALID_PACKAGE_NAME = 2000; // 0x7d0
     field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED = 3000; // 0xbb8
     field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE = 5000; // 0x1388
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1e9e9be..9eddab5 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -173,16 +173,6 @@
     SAM-compatible parameters (such as parameter 1, "sessionListener", in android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.session.MediaSessionManager#addOnSession2TokensChangedListener(android.media.session.MediaSessionManager.OnSession2TokensChangedListener, android.os.Handler):
     SAM-compatible parameters (such as parameter 1, "listener", in android.media.session.MediaSessionManager.addOnSession2TokensChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
-    SAM-compatible parameters (such as parameter 2, "callback", in android.nfc.NfcAdapter.enableReaderMode) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
-    SAM-compatible parameters (such as parameter 3, "tagRemovedListener", in android.nfc.NfcAdapter.ignore) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-SamShouldBeLast: android.nfc.NfcAdapter#setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity):
-    SAM-compatible parameters (such as parameter 1, "callback", in android.nfc.NfcAdapter.setBeamPushUrisCallback) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-SamShouldBeLast: android.nfc.NfcAdapter#setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...):
-    SAM-compatible parameters (such as parameter 1, "callback", in android.nfc.NfcAdapter.setNdefPushMessageCallback) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-SamShouldBeLast: android.nfc.NfcAdapter#setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...):
-    SAM-compatible parameters (such as parameter 1, "callback", in android.nfc.NfcAdapter.setOnNdefPushCompleteCallback) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.os.Binder#attachInterface(android.os.IInterface, String):
     SAM-compatible parameters (such as parameter 1, "owner", in android.os.Binder.attachInterface) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.os.Binder#linkToDeath(android.os.IBinder.DeathRecipient, int):
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 1c10356..2c5acf1 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -140,17 +140,6 @@
 
 }
 
-package android.nfc {
-
-  public final class NfcAdapter {
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
-    method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
-    field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
-  }
-
-}
-
 package android.os {
 
   public class Build {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d01706d..4402576 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -154,6 +154,7 @@
     field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
     field public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 8; // 0x8
     field public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 32; // 0x20
+    field public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 5; // 0x5
     field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
     field public static final int PROCESS_STATE_TOP = 2; // 0x2
     field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
@@ -2582,7 +2583,6 @@
     method @NonNull public static String convert(@NonNull java.util.UUID);
     method @Nullable public String getCloudMediaProvider();
     method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int);
-    method public static boolean isUserKeyUnlocked(int);
     field public static final String CACHE_RESERVE_PERCENT_HIGH_KEY = "cache_reserve_percent_high";
     field public static final String CACHE_RESERVE_PERCENT_LOW_KEY = "cache_reserve_percent_low";
     field public static final String STORAGE_THRESHOLD_PERCENT_HIGH_KEY = "storage_threshold_percent_high";
diff --git a/core/java/Android.bp b/core/java/Android.bp
index d0b958e..0ad621d 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -14,20 +14,12 @@
     hdrs: ["android/hardware/HardwareBuffer.aidl"],
 }
 
-// TODO (b/303286040): Remove this once |ENABLE_NFC_MAINLINE_FLAG| is rolled out
-filegroup {
-    name: "framework-core-nfc-infcadapter-sources",
-    srcs: [
-        "android/nfc/INfcAdapter.aidl",
-    ],
-    visibility: ["//frameworks/base/services/core"],
-}
-
 filegroup {
     name: "framework-core-sources",
     srcs: [
         "**/*.java",
         "**/*.aidl",
+        ":framework-nfc-non-updatable-sources",
     ],
     visibility: ["//frameworks/base"],
 }
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index f33d299..4823f44 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1311,8 +1311,9 @@
         if (!node.mEnded) {
             float durationScale = ValueAnimator.getDurationScale();
             durationScale = durationScale == 0  ? 1 : durationScale;
-            node.mEnded = node.mAnimation.pulseAnimationFrame(
-                    (long) (animPlayTime * durationScale));
+            if (node.mAnimation.pulseAnimationFrame((long) (animPlayTime * durationScale))) {
+                node.mEnded = true;
+            }
         }
     }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 66d04a3..f72a9de 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -705,6 +705,8 @@
 
     /** @hide Process is hosting a foreground service due to a system binding. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+    @TestApi
     public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE =
             ProcessStateEnum.BOUND_FOREGROUND_SERVICE;
 
@@ -3541,7 +3543,7 @@
          * foreground.  This may be running a window that is behind the current
          * foreground (so paused and with its state saved, not interacting with
          * the user, but visible to them to some degree); it may also be running
-         * other services under the system's control that it inconsiders important.
+         * other services under the system's control that it considers important.
          */
         public static final int IMPORTANCE_VISIBLE = 200;
 
@@ -3613,9 +3615,9 @@
         public static final int IMPORTANCE_CANT_SAVE_STATE = 350;
 
         /**
-         * Constant for {@link #importance}: This process process contains
-         * cached code that is expendable, not actively running any app components
-         * we care about.
+         * Constant for {@link #importance}: This process contains cached code
+         * that is expendable, not actively running any app components we care
+         * about.
          */
         public static final int IMPORTANCE_CACHED = 400;
 
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index c072feb..1073478 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -717,28 +717,28 @@
     /**
      * Returns whether the device is currently locked for the user.
      * <p>
-     * This returns the device locked state for the {@link Context}'s user. If this user is the
-     * current user, then the device is considered "locked" when the lock screen is showing (i.e.
-     * {@link #isKeyguardLocked()} returns {@code true}) and is not trivially dismissible (e.g. with
-     * swipe), and the user has a PIN, pattern, or password.
+     * This method returns the device locked state for the {@link Context}'s user. The device is
+     * considered to be locked for a user when the user's apps are currently inaccessible and some
+     * form of lock screen authentication is required to regain access to them. The lock screen
+     * authentication typically uses PIN, pattern, password, or biometric. Some devices may support
+     * additional methods, such as unlock using a paired smartwatch. "Swipe" does not count as
+     * authentication; if the lock screen is dismissible with swipe, for example due to the lock
+     * screen being set to Swipe or due to the device being kept unlocked by being near a trusted
+     * bluetooth device or in a trusted location, the device is considered unlocked.
+     * <div class="note">
      * <p>
-     * Note: the above definition implies that a user with no PIN, pattern, or password is never
-     * considered locked, even if the lock screen is showing and requesting a SIM card PIN. The
-     * device PIN and SIM PIN are separate. Also, the user is not considered locked if face
-     * authentication has just completed or a trust agent is keeping the device unlocked, since in
-     * these cases the lock screen is dismissible with swipe.
+     * <b>Note:</b> In the case of multiple full users, each user can have their own lock screen
+     * authentication configured. The device-locked state may differ between different users. For
+     * example, the device may be unlocked for the current user, but locked for a non-current user
+     * if lock screen authentication would be required to access that user's apps after switching to
+     * that user.
      * <p>
-     * For a user that is not the current user but can be switched to (usually this means "another
-     * full user"), and that has a PIN, pattern, or password, the device is always considered
-     * locked.
-     * <p>
-     * For a profile with a unified challenge, the device locked state is the same as that of the
-     * parent user.
-     * <p>
-     * For a profile with a separate challenge, the device becomes unlocked when the profile's PIN,
-     * pattern, password, or biometric is verified. It becomes locked when the parent user becomes
-     * locked, the screen turns off, the device reboots, the device policy controller locks the
-     * profile, or the timeout set by the device policy controller expires.
+     * In the case of a profile, when the device goes to the main lock screen, up to two layers of
+     * authentication may be required to regain access to the profile's apps: one to unlock the main
+     * lock screen, and one to unlock the profile (when a separate profile challenge is required).
+     * For a profile, the device is considered to be locked as long as any challenge remains, either
+     * the parent user's challenge (when applicable) or the profile's challenge (when applicable).
+     * </div>
      *
      * @return {@code true} if the device is currently locked for the user
      * @see #isKeyguardLocked()
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 27636f2..41d7932 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -82,8 +82,8 @@
 per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/server/pm/OWNERS
 
 # BackgroundInstallControlManager
-per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS
-per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS
+per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
+per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
 
 # ResourcesManager
 per-file ResourcesManager.java = file:RESOURCES_OWNERS
diff --git a/core/java/android/app/contextualsearch/OWNERS b/core/java/android/app/contextualsearch/OWNERS
new file mode 100644
index 0000000..0c2612c
--- /dev/null
+++ b/core/java/android/app/contextualsearch/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/contextualsearch/OWNERS
diff --git a/core/java/android/app/wearable/OWNERS b/core/java/android/app/wearable/OWNERS
index 073e2d7..497eaf0 100644
--- a/core/java/android/app/wearable/OWNERS
+++ b/core/java/android/app/wearable/OWNERS
@@ -1,3 +1,5 @@
 charliewang@google.com
+hackz@google.com
 oni@google.com
+tomchan@google.com
 volnov@google.com
\ No newline at end of file
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 2200af6..c5356fe 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -182,7 +182,7 @@
      *
      * @param context A Context object which should be some mock instance (like the
      * instance of {@link android.test.mock.MockContext}).
-     * @param readPermission The read permision you want this instance should have in the
+     * @param readPermission The read permission you want this instance should have in the
      * test, which is available via {@link #getReadPermission()}.
      * @param writePermission The write permission you want this instance should have
      * in the test, which is available via {@link #getWritePermission()}.
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index b2cd7e9..a97ca0a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -387,7 +387,7 @@
      * {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
      * <li>When querying a provider, where no QUERY_ARG_SQL* otherwise exists in
      * the arguments {@link Bundle}, the Content framework will attempt to
-     * synthesize an QUERY_ARG_SQL* argument using the corresponding
+     * synthesize a QUERY_ARG_SQL* argument using the corresponding
      * QUERY_ARG_SORT* values.
      */
     public static final String QUERY_ARG_SORT_COLUMNS = "android:query-arg-sort-columns";
diff --git a/core/java/android/content/OWNERS b/core/java/android/content/OWNERS
index 90c3d04..a37408b 100644
--- a/core/java/android/content/OWNERS
+++ b/core/java/android/content/OWNERS
@@ -4,6 +4,7 @@
 per-file *Content* = file:/services/core/java/com/android/server/am/OWNERS
 per-file *Sync* = file:/services/core/java/com/android/server/am/OWNERS
 per-file IntentFilter.java = file:/PACKAGE_MANAGER_OWNERS
+per-file UriRelativeFilter* = file:/PACKAGE_MANAGER_OWNERS
 per-file IntentFilter.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file Intent.java = file:/INTENT_OWNERS
 per-file AutofillOptions* = file:/core/java/android/service/autofill/OWNERS
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index 0a7d079..5bdcc1c 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -9,3 +9,4 @@
 per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
 per-file UserInfo* = file:/MULTIUSER_OWNERS
 per-file *UserProperties* = file:/MULTIUSER_OWNERS
+per-file IBackgroundInstallControlService.aidl = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 462df07..920cc57 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -34,6 +34,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -2370,6 +2371,8 @@
         public DataLoaderParams dataLoaderParams;
         /** {@hide} */
         public int rollbackDataPolicy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE;
+        /** @hide */
+        public long rollbackLifetimeMillis = 0;
         /** {@hide} */
         public boolean forceQueryableOverride;
         /** {@hide} */
@@ -2424,6 +2427,7 @@
                 dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel);
             }
             rollbackDataPolicy = source.readInt();
+            rollbackLifetimeMillis = source.readLong();
             requireUserAction = source.readInt();
             packageSource = source.readInt();
             applicationEnabledSettingPersistent = source.readBoolean();
@@ -2456,6 +2460,7 @@
             ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
             ret.dataLoaderParams = dataLoaderParams;
             ret.rollbackDataPolicy = rollbackDataPolicy;
+            ret.rollbackLifetimeMillis = rollbackLifetimeMillis;
             ret.requireUserAction = requireUserAction;
             ret.packageSource = packageSource;
             ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
@@ -2737,12 +2742,7 @@
          */
         @SystemApi
         public void setEnableRollback(boolean enable) {
-            if (enable) {
-                installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
-            } else {
-                installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
-            }
-            rollbackDataPolicy = PackageManager.ROLLBACK_DATA_POLICY_RESTORE;
+            setEnableRollback(enable, PackageManager.ROLLBACK_DATA_POLICY_RESTORE);
         }
 
         /**
@@ -2766,10 +2766,36 @@
                 installFlags |= PackageManager.INSTALL_ENABLE_ROLLBACK;
             } else {
                 installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
+                rollbackLifetimeMillis = 0;
             }
             rollbackDataPolicy = dataPolicy;
         }
 
+        /**
+         * If rollback enabled for this session (via {@link #setEnableRollback}, set time
+         * after which rollback will no longer be possible
+         *
+         * <p>For multi-package installs, this value must be set on the parent session.
+         * Child session rollback lifetime will be ignored.
+         *
+         * @param lifetimeMillis time after which rollback expires
+         * @throws IllegalArgumentException if lifetimeMillis is negative or rollback is not
+         * enabled via setEnableRollback.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
+        @FlaggedApi(Flags.FLAG_ROLLBACK_LIFETIME)
+        public void setRollbackLifetimeMillis(@DurationMillisLong long lifetimeMillis) {
+            if (lifetimeMillis < 0) {
+                throw new IllegalArgumentException("rollbackLifetimeMillis can't be negative.");
+            }
+            if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+                throw new IllegalArgumentException(
+                        "Can't set rollbackLifetimeMillis when rollback is not enabled");
+            }
+            rollbackLifetimeMillis = lifetimeMillis;
+        }
 
         /**
          * @deprecated use {@link #setRequestDowngrade(boolean)}.
@@ -3124,6 +3150,7 @@
             pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
             pw.printPair("dataLoaderParams", dataLoaderParams);
             pw.printPair("rollbackDataPolicy", rollbackDataPolicy);
+            pw.printPair("rollbackLifetimeMillis", rollbackLifetimeMillis);
             pw.printPair("applicationEnabledSettingPersistent",
                     applicationEnabledSettingPersistent);
             pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
@@ -3165,6 +3192,7 @@
                 dest.writeParcelable(null, flags);
             }
             dest.writeInt(rollbackDataPolicy);
+            dest.writeLong(rollbackLifetimeMillis);
             dest.writeInt(requireUserAction);
             dest.writeInt(packageSource);
             dest.writeBoolean(applicationEnabledSettingPersistent);
@@ -3358,6 +3386,9 @@
         /** {@hide} */
         public int rollbackDataPolicy;
 
+        /** @hide */
+        public long rollbackLifetimeMillis;
+
         /** {@hide} */
         public int requireUserAction;
 
@@ -3425,6 +3456,7 @@
             isCommitted = source.readBoolean();
             isPreapprovalRequested = source.readBoolean();
             rollbackDataPolicy = source.readInt();
+            rollbackLifetimeMillis = source.readLong();
             createdMillis = source.readLong();
             requireUserAction = source.readInt();
             installerUid = source.readInt();
@@ -4048,6 +4080,7 @@
             dest.writeBoolean(isCommitted);
             dest.writeBoolean(isPreapprovalRequested);
             dest.writeInt(rollbackDataPolicy);
+            dest.writeLong(rollbackLifetimeMillis);
             dest.writeLong(createdMillis);
             dest.writeInt(requireUserAction);
             dest.writeInt(installerUid);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c8ba47b..1bc0418 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4276,6 +4276,10 @@
      * the Android Keystore backed by an isolated execution environment. The version indicates
      * which features are implemented in the isolated execution environment:
      * <ul>
+     * <li>300: Ability to include a second IMEI in the ID attestation record, see
+     * {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
+     * <li>200: Hardware support for Curve 25519 (including both Ed25519 signature generation and
+     * X25519 key agreement).
      * <li>100: Hardware support for ECDH (see {@link javax.crypto.KeyAgreement}) and support
      * for app-generated attestation keys (see {@link
      * android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias(String)}).
@@ -4305,6 +4309,11 @@
      * StrongBox</a>. If this feature has a version, the version number indicates which features are
      * implemented in StrongBox:
      * <ul>
+     * <li>300: Ability to include a second IMEI in the ID attestation record, see
+     * {@link android.app.admin.DevicePolicyManager#ID_TYPE_IMEI}.
+     * <li>200: No new features for StrongBox (the Android Keystore environment backed by an
+     * isolated execution environment has gained support for Curve 25519 in this version, but
+     * the implementation backed by a dedicated secure processor is not expected to implement it).
      * <li>100: Hardware support for ECDH (see {@link javax.crypto.KeyAgreement}) and support
      * for app-generated attestation keys (see {@link
      * android.security.keystore.KeyGenParameterSpec.Builder#setAttestKeyAlias(String)}).
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 5242533..d07de72 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -6,3 +6,11 @@
     description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module."
     bug: "304741685"
 }
+
+flag {
+    name: "rollback_lifetime"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable custom rollback lifetime during install."
+    bug: "299670324"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/verify/domain/OWNERS b/core/java/android/content/pm/verify/domain/OWNERS
index c669112..b451fe4 100644
--- a/core/java/android/content/pm/verify/domain/OWNERS
+++ b/core/java/android/content/pm/verify/domain/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 36137
+include /PACKAGE_MANAGER_OWNERS
 
-chiuwinson@google.com
-patb@google.com
-toddke@google.com
\ No newline at end of file
+wloh@google.com
\ No newline at end of file
diff --git a/core/java/android/content/rollback/OWNERS b/core/java/android/content/rollback/OWNERS
index 3093fd6..8e5a0d8 100644
--- a/core/java/android/content/rollback/OWNERS
+++ b/core/java/android/content/rollback/OWNERS
@@ -1,5 +1,5 @@
-# Bug component: 557916
+# Bug component: 819107
 
-narayan@google.com
-nandana@google.com
-olilan@google.com
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index afa1c20..c00c18c 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -510,7 +510,7 @@
     Bundle getExtras();
 
     /**
-     * This is an out-of-band way for the the user of a cursor to communicate with the cursor. The
+     * This is an out-of-band way for the user of a cursor to communicate with the cursor. The
      * structure of each bundle is entirely defined by the cursor.
      *
      * <p>One use of this is to tell a cursor that it should retry its network request after it
diff --git a/core/java/android/hardware/radio/OWNERS b/core/java/android/hardware/radio/OWNERS
index 302fdd7..51a85e4 100644
--- a/core/java/android/hardware/radio/OWNERS
+++ b/core/java/android/hardware/radio/OWNERS
@@ -1,4 +1,3 @@
 xuweilin@google.com
 oscarazu@google.com
 ericjeong@google.com
-keunyoung@google.com
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 365f913..594ec18 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 import static android.app.ActivityManager.procStateToString;
 import static android.content.pm.PackageManager.GET_SIGNATURES;
@@ -170,6 +171,8 @@
     public static final String FIREWALL_CHAIN_NAME_RESTRICTED = "restricted";
     /** @hide */
     public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby";
+    /** @hide */
+    public static final String FIREWALL_CHAIN_NAME_BACKGROUND = "background";
 
     private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
 
@@ -180,6 +183,9 @@
     /** @hide */
     public static final int TOP_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_TOP;
 
+    /** @hide */
+    public static final int BACKGROUND_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+
     /**
      * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
      * applies to.
@@ -264,6 +270,16 @@
      * @hide
      */
     public static final int ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST = 1 << 6;
+
+    /**
+     * Flag to indicate that the app is exempt from always-on background network restrictions.
+     * Note that this is explicitly different to the flag NOT_FOREGROUND which is used to grant
+     * shared exception to apps from power restrictions like doze, battery saver and app-standby.
+     *
+     * @hide
+     */
+    public static final int ALLOWED_REASON_NOT_IN_BACKGROUND = 1 << 7;
+
     /**
      * Flag to indicate that app is exempt from certain metered network restrictions because user
      * explicitly exempted it.
@@ -822,6 +838,21 @@
     }
 
     /**
+     * This is currently only used as an implementation detail for
+     * {@link com.android.server.net.NetworkPolicyManagerService}.
+     * Only put here to be together with other isProcStateAllowed* methods.
+     *
+     * @hide
+     */
+    public static boolean isProcStateAllowedNetworkWhileBackground(@Nullable UidState uidState) {
+        if (uidState == null) {
+            return false;
+        }
+        return uidState.procState < BACKGROUND_THRESHOLD_STATE
+                || (uidState.capability & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0;
+    }
+
+    /**
      * Returns true if {@param procState} is considered foreground and as such will be allowed
      * to access network when the device is in data saver mode. Otherwise, false.
      * @hide
diff --git a/core/java/android/net/thread/OWNERS b/core/java/android/net/thread/OWNERS
new file mode 100644
index 0000000..55c307b
--- /dev/null
+++ b/core/java/android/net/thread/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1203089
+
+include platform/packages/modules/ThreadNetwork:/OWNERS
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
new file mode 100644
index 0000000..6e72f8e
--- /dev/null
+++ b/core/java/android/net/thread/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.net.thread.flags"
+
+flag {
+    name: "thread_user_restriction_enabled"
+    namespace: "thread_network"
+    description: "Controls whether user restriction on thread networks is enabled"
+    bug: "307679182"
+}
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 70cf973..83b7eda 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -80,8 +80,6 @@
      * <p>The VCN will only migrate to a Carrier WiFi network that has a signal strength greater
      * than, or equal to this threshold.
      *
-     * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup.
-     *
      * @hide
      */
     @NonNull
@@ -94,14 +92,39 @@
      * <p>If the VCN's selected Carrier WiFi network has a signal strength less than this threshold,
      * the VCN will attempt to migrate away from the Carrier WiFi network.
      *
-     * <p>WARNING: The VCN does not listen for changes to this key made after VCN startup.
-     *
      * @hide
      */
     @NonNull
     public static final String VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY =
             "vcn_network_selection_wifi_exit_rssi_threshold";
 
+    /**
+     * Key for the interval to poll IpSecTransformState for packet loss monitoring
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY =
+            "vcn_network_selection_poll_ipsec_state_interval_seconds";
+
+    /**
+     * Key for the threshold of IPSec packet loss rate
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY =
+            "vcn_network_selection_ipsec_packet_loss_percent_threshold";
+
+    /**
+     * Key for the list of timeouts in minute to stop penalizing an underlying network candidate
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY =
+            "vcn_network_selection_penalty_timeout_minutes_list";
+
     // TODO: Add separate signal strength thresholds for 2.4 GHz and 5GHz
 
     /**
@@ -115,6 +138,20 @@
             "vcn_restricted_transports";
 
     /**
+     * Key for number of seconds to wait before entering safe mode
+     *
+     * <p>A VcnGatewayConnection will enter safe mode when it takes over the configured timeout to
+     * enter {@link ConnectedState}.
+     *
+     * <p>Defaults to 30, unless overridden by carrier config
+     *
+     * @hide
+     */
+    @NonNull
+    public static final String VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY =
+            "vcn_safe_mode_timeout_seconds_key";
+
+    /**
      * Key for maximum number of parallel SAs for tunnel aggregation
      *
      * <p>If set to a value > 1, multiple tunnels will be set up, and inbound traffic will be
@@ -134,7 +171,11 @@
             new String[] {
                 VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY,
                 VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
+                VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
+                VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+                VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
                 VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
                 VCN_TUNNEL_AGGREGATION_SA_COUNT_MAX_KEY,
             };
 
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 6956916..7afd721 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -5,4 +5,18 @@
     namespace: "vcn"
     description: "Feature flag for safe mode configurability"
     bug: "276358140"
+}
+
+flag {
+    name: "safe_mode_timeout_config"
+    namespace: "vcn"
+    description: "Feature flag for adjustable safe mode timeout"
+    bug: "317406085"
+}
+
+flag{
+    name: "network_metric_monitor"
+    namespace: "vcn"
+    description: "Feature flag for enabling network metric monitor"
+    bug: "282996138"
 }
\ No newline at end of file
diff --git a/core/java/android/nfc/OWNERS b/core/java/android/nfc/OWNERS
deleted file mode 100644
index 35e9713..0000000
--- a/core/java/android/nfc/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 48448
-include platform/packages/apps/Nfc:/OWNERS
diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING
deleted file mode 100644
index 5b5ea37..0000000
--- a/core/java/android/nfc/TEST_MAPPING
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "NfcManagerTests"
-    },
-    {
-      "name": "CtsNfcTestCases"
-    }
-  ]
-}
diff --git a/core/java/android/os/ArtModuleServiceManager.java b/core/java/android/os/ArtModuleServiceManager.java
index 0009e61..e0b631d 100644
--- a/core/java/android/os/ArtModuleServiceManager.java
+++ b/core/java/android/os/ArtModuleServiceManager.java
@@ -15,9 +15,11 @@
  */
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.pm.Flags;
 
 /**
  * Provides a way to register and obtain the system service binder objects managed by the ART
@@ -60,4 +62,18 @@
     public ServiceRegisterer getArtdServiceRegisterer() {
         return new ServiceRegisterer("artd");
     }
+
+    /** Returns {@link ServiceRegisterer} for the "artd_pre_reboot" service. */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
+    public ServiceRegisterer getArtdPreRebootServiceRegisterer() {
+        return new ServiceRegisterer("artd_pre_reboot");
+    }
+
+    /** Returns {@link ServiceRegisterer} for the "dexopt_chroot_setup" service. */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
+    public ServiceRegisterer getDexoptChrootSetupServiceRegisterer() {
+        return new ServiceRegisterer("dexopt_chroot_setup");
+    }
 }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a9b7257..5871717 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1315,9 +1315,7 @@
         if (IS_ENG) return true;
 
         if (IS_TREBLE_ENABLED) {
-            // If we can run this code, the device should already pass AVB.
-            // So, we don't need to check AVB here.
-            int result = VintfObject.verifyWithoutAvb();
+            int result = VintfObject.verifyBuildAtBoot();
 
             if (result != 0) {
                 Slog.e(TAG, "Vendor interface is incompatible, error="
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 5b24dca..3de8628 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -25,6 +25,7 @@
 import static android.system.OsConstants.EINVAL;
 import static android.system.OsConstants.ENOSYS;
 import static android.system.OsConstants.F_OK;
+import static android.system.OsConstants.EIO;
 import static android.system.OsConstants.O_ACCMODE;
 import static android.system.OsConstants.O_APPEND;
 import static android.system.OsConstants.O_CREAT;
@@ -37,6 +38,7 @@
 import static android.system.OsConstants.SPLICE_F_MOVE;
 import static android.system.OsConstants.S_ISFIFO;
 import static android.system.OsConstants.S_ISREG;
+import static android.system.OsConstants.S_ISSOCK;
 import static android.system.OsConstants.W_OK;
 
 import android.annotation.NonNull;
@@ -459,6 +461,8 @@
                     }
                 } else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
                     return copyInternalSplice(in, out, count, signal, executor, listener);
+                } else if (S_ISSOCK(st_in.st_mode) || S_ISSOCK(st_out.st_mode)) {
+                    return copyInternalSpliceSocket(in, out, count, signal, executor, listener);
                 }
             } catch (ErrnoException e) {
                 throw e.rethrowAsIOException();
@@ -509,6 +513,86 @@
         }
         return progress;
     }
+    /**
+     * Requires one of input or output to be a socket file.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    public static long copyInternalSpliceSocket(FileDescriptor in, FileDescriptor out, long count,
+            CancellationSignal signal, Executor executor, ProgressListener listener)
+            throws ErrnoException {
+        long progress = 0;
+        long checkpoint = 0;
+        long countToRead = count;
+        long countInPipe = 0;
+        long t;
+
+        FileDescriptor[] pipes = Os.pipe();
+
+        while (countToRead > 0 || countInPipe > 0) {
+            if (countToRead > 0) {
+                t = Os.splice(in, null, pipes[1], null, Math.min(countToRead, COPY_CHECKPOINT_BYTES),
+                              SPLICE_F_MOVE | SPLICE_F_MORE);
+                if (t < 0) {
+                    // splice error
+                    Slog.e(TAG, "splice error, fdIn --> pipe, copy size:" + count +
+                           ", copied:" + progress +
+                           ", read:" + (count - countToRead) +
+                           ", in pipe:" + countInPipe);
+                    break;
+                } else if (t == 0) {
+                    // end of input, input count larger than real size
+                    Slog.w(TAG, "Reached the end of the input file. The size to be copied exceeds the actual size, copy size:" + count +
+                           ", copied:" + progress +
+                           ", read:" + (count - countToRead) +
+                           ", in pipe:" + countInPipe);
+                    countToRead = 0;
+                } else {
+                    countInPipe += t;
+                    countToRead -= t;
+                }
+            }
+
+            if (countInPipe > 0) {
+                t = Os.splice(pipes[0], null, out, null, Math.min(countInPipe, COPY_CHECKPOINT_BYTES),
+                              SPLICE_F_MOVE | SPLICE_F_MORE);
+                // The data is already in the pipeline, so the return value will not be zero.
+                // If it is 0, it means an error has occurred. So here use t<=0.
+                if (t <= 0) {
+                    Slog.e(TAG, "splice error, pipe --> fdOut, copy size:" + count +
+                           ", copied:" + progress +
+                           ", read:" + (count - countToRead) +
+                           ", in pipe: " + countInPipe);
+                    throw new ErrnoException("splice, pipe --> fdOut", EIO);
+                } else {
+                    progress += t;
+                    checkpoint += t;
+                    countInPipe -= t;
+                }
+            }
+
+            if (checkpoint >= COPY_CHECKPOINT_BYTES) {
+                if (signal != null) {
+                    signal.throwIfCanceled();
+                }
+                if (executor != null && listener != null) {
+                    final long progressSnapshot = progress;
+                    executor.execute(() -> {
+                        listener.onProgress(progressSnapshot);
+                    });
+                }
+                checkpoint = 0;
+            }
+        }
+        if (executor != null && listener != null) {
+            final long progressSnapshot = progress;
+            executor.execute(() -> {
+                listener.onProgress(progressSnapshot);
+            });
+        }
+        return progress;
+    }
 
     /**
      * Requires both input and output to be a regular file.
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 8fcff78..3149de4 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -673,6 +673,7 @@
         if (anglePkg.isEmpty()) {
             return;
         }
+        intent.setPackage(anglePkg);
 
         context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
             @Override
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index 61b24aa..b7649ba 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -52,4 +52,9 @@
      * @see SystemConfigManager#getDefaultVrComponents
      */
     List<ComponentName> getDefaultVrComponents();
+
+    /**
+     * @see SystemConfigManager#getPreventUserDisablePackages
+     */
+    List<String> getPreventUserDisablePackages();
 }
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 7f60a20..2145c1a 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -90,4 +90,8 @@
 per-file Temperature.java = file:/THERMAL_OWNERS
 
 # SecurityStateManager
-per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
\ No newline at end of file
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
+
+# SystemConfig
+per-file ISystemConfig.aidl = file:/PACKAGE_MANAGER_OWNERS
+per-file SystemConfigManager.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 9b77b79..bccea93 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1471,7 +1471,15 @@
     @UnsupportedAppUsage
     public static final native long getPss(int pid);
 
-    /** @hide */
+    /**
+     * Gets the total Rss value for a given process, in bytes.
+     *
+     * @param pid the process to the Rss for
+     * @return an ordered array containing multiple values, they are:
+     *  [total_rss, file, anon, swap, shmem].
+     *  or NULL if the value cannot be determined
+     * @hide
+     */
     public static final native long[] getRss(int pid);
 
     /**
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index a3b836a..d002fe1 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -18,8 +18,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -47,11 +45,8 @@
 import android.util.Log;
 import android.view.Display;
 
-import libcore.io.Streams;
-
 import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileWriter;
 import java.io.IOException;
@@ -73,7 +68,6 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
 
 import sun.security.pkcs.PKCS7;
 import sun.security.pkcs.SignerInfo;
@@ -423,72 +417,43 @@
         } finally {
             raf.close();
         }
-
-        // Additionally verify the package compatibility.
-        if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
-            throw new SignatureException("package compatibility verification failed");
-        }
     }
 
     /**
      * Verifies the compatibility entry from an {@link InputStream}.
      *
-     * @return the verification result.
+     * @param inputStream The stream that contains the package compatibility info.
+     * @throws IOException Never.
+     * @return {@code true}.
+     * @deprecated This function no longer checks {@code inputStream} and
+     *   unconditionally returns true. Instead, check compatibility when the
+     *   OTA package is generated.
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(
+            publicAlternatives = "Use {@code true} directly",
+            maxTargetSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     private static boolean verifyPackageCompatibility(InputStream inputStream) throws IOException {
-        ArrayList<String> list = new ArrayList<>();
-        ZipInputStream zis = new ZipInputStream(inputStream);
-        ZipEntry entry;
-        while ((entry = zis.getNextEntry()) != null) {
-            long entrySize = entry.getSize();
-            if (entrySize > Integer.MAX_VALUE || entrySize < 0) {
-                throw new IOException(
-                        "invalid entry size (" + entrySize + ") in the compatibility file");
-            }
-            byte[] bytes = new byte[(int) entrySize];
-            Streams.readFully(zis, bytes);
-            list.add(new String(bytes, UTF_8));
-        }
-        if (list.isEmpty()) {
-            throw new IOException("no entries found in the compatibility file");
-        }
-        return (VintfObject.verify(list.toArray(new String[list.size()])) == 0);
-    }
-
-    /**
-     * Reads and verifies the compatibility entry in an OTA zip package. The compatibility entry is
-     * a zip file (inside the OTA package zip).
-     *
-     * @return {@code true} if the entry doesn't exist or verification passes.
-     */
-    private static boolean readAndVerifyPackageCompatibilityEntry(File packageFile)
-            throws IOException {
-        try (ZipFile zip = new ZipFile(packageFile)) {
-            ZipEntry entry = zip.getEntry("compatibility.zip");
-            if (entry == null) {
-                return true;
-            }
-            InputStream inputStream = zip.getInputStream(entry);
-            return verifyPackageCompatibility(inputStream);
-        }
+        return true;
     }
 
     /**
      * Verifies the package compatibility info against the current system.
      *
      * @param compatibilityFile the {@link File} that contains the package compatibility info.
-     * @throws IOException if there were any errors reading the compatibility file.
-     * @return the compatibility verification result.
+     * @throws IOException Never.
+     * @return {@code true}
+     * @deprecated This function no longer checks {@code compatibilityFile} and
+     *   unconditionally returns true. Instead, check compatibility when the
+     *   OTA package is generated.
      *
      * {@hide}
      */
+    @Deprecated
     @SystemApi
     @SuppressLint("RequiresPermission")
     public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException {
-        try (InputStream inputStream = new FileInputStream(compatibilityFile)) {
-            return verifyPackageCompatibility(inputStream);
-        }
+        return true;
     }
 
     /**
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index 77843d9..21ffbf1 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -161,4 +161,18 @@
         }
         return Collections.emptyList();
     }
+
+    /**
+     * Return the packages that are prevented from being disabled, where if
+     * disabled it would result in a non-functioning system or similar.
+     * @hide
+     */
+    @NonNull
+    public List<String> getPreventUserDisablePackages() {
+        try {
+            return mInterface.getPreventUserDisablePackages();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/UpdateEngineStable.java b/core/java/android/os/UpdateEngineStable.java
new file mode 100644
index 0000000..9e2593e
--- /dev/null
+++ b/core/java/android/os/UpdateEngineStable.java
@@ -0,0 +1,192 @@
+/*
+ * 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.annotation.IntDef;
+
+/**
+ * UpdateEngineStable handles calls to the update engine stalbe which takes care of A/B OTA updates.
+ * This interface has lesser functionalities than UpdateEngine and doesn't allow cancel.
+ *
+ * <p>The minimal flow is:
+ *
+ * <ol>
+ *   <li>Create a new UpdateEngineStable instance.
+ *   <li>Call {@link #bind}, provide callback function.
+ *   <li>Call {@link #applyPayloadFd}.
+ * </ol>
+ *
+ * The APIs defined in this class and UpdateEngineStableCallback class must be in sync with the ones
+ * in {@code system/update_engine/stable/android/os/IUpdateEngineStable.aidl} and {@code
+ * ssystem/update_engine/stable/android/os/IUpdateEngineStableCallback.aidl}.
+ *
+ * @hide
+ */
+public class UpdateEngineStable {
+    private static final String TAG = "UpdateEngineStable";
+
+    private static final String UPDATE_ENGINE_STABLE_SERVICE =
+            "android.os.UpdateEngineStableService";
+
+    /**
+     * Error codes from update engine upon finishing a call to {@link applyPayloadFd}. Values will
+     * be passed via the callback function {@link
+     * UpdateEngineStableCallback#onPayloadApplicationComplete}. Values must agree with the ones in
+     * {@code system/update_engine/common/error_code.h}.
+     */
+    /** @hide */
+    @IntDef(
+            value = {
+                UpdateEngine.ErrorCodeConstants.SUCCESS,
+                UpdateEngine.ErrorCodeConstants.ERROR,
+                UpdateEngine.ErrorCodeConstants.FILESYSTEM_COPIER_ERROR,
+                UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
+                UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
+                UpdateEngine.ErrorCodeConstants.KERNEL_DEVICE_OPEN_ERROR,
+                UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
+                UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
+                UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
+                UpdateEngine.ErrorCodeConstants.NOT_ENOUGH_SPACE,
+                UpdateEngine.ErrorCodeConstants.DEVICE_CORRUPTED,
+            })
+    public @interface ErrorCode {}
+
+    private final IUpdateEngineStable mUpdateEngineStable;
+    private IUpdateEngineStableCallback mUpdateEngineStableCallback = null;
+    private final Object mUpdateEngineStableCallbackLock = new Object();
+
+    /**
+     * Creates a new instance.
+     *
+     * @hide
+     */
+    public UpdateEngineStable() {
+        mUpdateEngineStable =
+                IUpdateEngineStable.Stub.asInterface(
+                        ServiceManager.getService(UPDATE_ENGINE_STABLE_SERVICE));
+        if (mUpdateEngineStable == null) {
+            throw new IllegalStateException("Failed to find " + UPDATE_ENGINE_STABLE_SERVICE);
+        }
+    }
+
+    /**
+     * Prepares this instance for use. The callback will be notified on any status change, and when
+     * the update completes. A handler can be supplied to control which thread runs the callback, or
+     * null.
+     *
+     * @hide
+     */
+    public boolean bind(final UpdateEngineStableCallback callback, final Handler handler) {
+        synchronized (mUpdateEngineStableCallbackLock) {
+            mUpdateEngineStableCallback =
+                    new IUpdateEngineStableCallback.Stub() {
+                        @Override
+                        public void onStatusUpdate(final int status, final float percent) {
+                            if (handler != null) {
+                                handler.post(
+                                        new Runnable() {
+                                            @Override
+                                            public void run() {
+                                                callback.onStatusUpdate(status, percent);
+                                            }
+                                        });
+                            } else {
+                                callback.onStatusUpdate(status, percent);
+                            }
+                        }
+
+                        @Override
+                        public void onPayloadApplicationComplete(final int errorCode) {
+                            if (handler != null) {
+                                handler.post(
+                                        new Runnable() {
+                                            @Override
+                                            public void run() {
+                                                callback.onPayloadApplicationComplete(errorCode);
+                                            }
+                                        });
+                            } else {
+                                callback.onPayloadApplicationComplete(errorCode);
+                            }
+                        }
+
+                        @Override
+                        public int getInterfaceVersion() {
+                            return super.VERSION;
+                        }
+
+                        @Override
+                        public String getInterfaceHash() {
+                            return super.HASH;
+                        }
+                    };
+
+            try {
+                return mUpdateEngineStable.bind(mUpdateEngineStableCallback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Equivalent to {@code bind(callback, null)}.
+     *
+     * @hide
+     */
+    public boolean bind(final UpdateEngineStableCallback callback) {
+        return bind(callback, null);
+    }
+
+    /**
+     * Applies payload from given ParcelFileDescriptor. Usage is same as UpdateEngine#applyPayload
+     *
+     * @hide
+     */
+    public void applyPayloadFd(
+            ParcelFileDescriptor fd, long offset, long size, String[] headerKeyValuePairs) {
+        try {
+            mUpdateEngineStable.applyPayloadFd(fd, offset, size, headerKeyValuePairs);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unbinds the last bound callback function.
+     *
+     * @hide
+     */
+    public boolean unbind() {
+        synchronized (mUpdateEngineStableCallbackLock) {
+            if (mUpdateEngineStableCallback == null) {
+                return true;
+            }
+            try {
+                boolean result = mUpdateEngineStable.unbind(mUpdateEngineStableCallback);
+                mUpdateEngineStableCallback = null;
+                return result;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/core/java/android/os/UpdateEngineStableCallback.java b/core/java/android/os/UpdateEngineStableCallback.java
new file mode 100644
index 0000000..4bcfb4b
--- /dev/null
+++ b/core/java/android/os/UpdateEngineStableCallback.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+/**
+ * Callback function for UpdateEngineStable. Used to keep the caller up to date with progress, so
+ * the UI (if any) can be updated.
+ *
+ * <p>The APIs defined in this class and UpdateEngineStable class must be in sync with the ones in
+ * system/update_engine/stable/android/os/IUpdateEngineStable.aidl and
+ * system/update_engine/stable/android/os/IUpdateEngineStableCallback.aidl.
+ *
+ * <p>{@hide}
+ */
+public abstract class UpdateEngineStableCallback {
+
+    /**
+     * Invoked when anything changes. The value of {@code status} will be one of the values from
+     * {@link UpdateEngine.UpdateStatusConstants}, and {@code percent} will be valid
+     *
+     * @hide
+     */
+    public abstract void onStatusUpdate(int status, float percent);
+
+    /**
+     * Invoked when the payload has been applied, whether successfully or unsuccessfully. The value
+     * of {@code errorCode} will be one of the values from {@link UpdateEngine.ErrorCodeConstants}.
+     *
+     * @hide
+     */
+    public abstract void onPayloadApplicationComplete(@UpdateEngineStable.ErrorCode int errorCode);
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 97b53d7..cdef20a 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1847,6 +1847,30 @@
             "no_near_field_communication_radio";
 
     /**
+     * This user restriction specifies if Thread network is disallowed on the device. If Thread
+     * network is disallowed it cannot be turned on via Settings.
+     *
+     * <p>This restriction can only be set by a device owner or a profile owner of an
+     * organization-owned managed profile on the parent profile.
+     * In both cases, the restriction applies globally on the device and will turn off the
+     * Thread network radio if it's currently on and prevent the radio from being turned
+     * on in the future.
+     *
+     * <p> <a href="https://www.threadgroup.org">Thread</a> is a low-power and low-latency wireless
+     * mesh networking protocol built on IPv6.
+     *
+     * <p>Default is <code>false</code>.
+     *
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+    public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
+
+    /**
      * List of key values that can be passed into the various user restriction related methods
      * in {@link UserManager} & {@link DevicePolicyManager}.
      * Note: This is slightly different from the real set of user restrictions listed in {@link
@@ -1931,6 +1955,7 @@
             DISALLOW_ULTRA_WIDEBAND_RADIO,
             DISALLOW_GRANT_ADMIN,
             DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+            DISALLOW_THREAD_NETWORK,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserRestrictionKey {}
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 1f11197..5056557 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.TestApi;
-import android.util.Slog;
 
 import java.util.Map;
 
@@ -32,6 +31,10 @@
 
     private static final String LOG_TAG = "VintfObject";
 
+    static {
+        System.loadLibrary("vintf_jni");
+    }
+
     /**
      * Slurps all device information (both manifests and both matrices)
      * and report them.
@@ -44,44 +47,8 @@
     public static native String[] report();
 
     /**
-     * Verify that the given metadata for an OTA package is compatible with
-     * this device.
-     *
-     * @param packageInfo a list of serialized form of HalManifest's /
-     * CompatibilityMatri'ces (XML).
-     * @return = 0 if success (compatible)
-     *         &gt; 0 if incompatible
-     *         &lt; 0 if any error (mount partition fails, illformed XML, etc.)
-     *
-     * @deprecated Checking compatibility against an OTA package is no longer
-     * supported because the format of VINTF metadata in the OTA package may not
-     * be recognized by the current system.
-     *
-     * <p>
-     * <ul>
-     * <li>This function always returns 0 for non-empty {@code packageInfo}.
-     * </li>
-     * <li>This function returns the result of {@link #verifyWithoutAvb} for
-     * null or empty {@code packageInfo}.</li>
-     * </ul>
-     *
-     * @hide
-     */
-    @Deprecated
-    public static int verify(String[] packageInfo) {
-        if (packageInfo != null && packageInfo.length > 0) {
-            Slog.w(LOG_TAG, "VintfObject.verify() with non-empty packageInfo is deprecated. "
-                    + "Skipping compatibility checks for update package.");
-            return 0;
-        }
-        Slog.w(LOG_TAG, "VintfObject.verify() is deprecated. Call verifyWithoutAvb() instead.");
-        return verifyWithoutAvb();
-    }
-
-    /**
-     * Verify Vintf compatibility on the device without checking AVB
-     * (Android Verified Boot). It is useful to verify a running system
-     * image where AVB check is irrelevant.
+     * Verify Vintf compatibility on the device at boot time. Certain checks
+     * like kernel checks, AVB checks are disabled.
      *
      * @return = 0 if success (compatible)
      *         > 0 if incompatible
@@ -89,7 +56,7 @@
      *
      * @hide
      */
-    public static native int verifyWithoutAvb();
+    public static native int verifyBuildAtBoot();
 
     /**
      * @return a list of HAL names and versions that is supported by this
diff --git a/core/java/android/os/VintfRuntimeInfo.java b/core/java/android/os/VintfRuntimeInfo.java
index f17039b..e729063 100644
--- a/core/java/android/os/VintfRuntimeInfo.java
+++ b/core/java/android/os/VintfRuntimeInfo.java
@@ -28,6 +28,10 @@
 
     private VintfRuntimeInfo() {}
 
+    static {
+        System.loadLibrary("vintf_jni");
+    }
+
     /**
      * @return /sys/fs/selinux/policyvers, via security_policyvers() native call
      *
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 3cb5c60..c0539d6 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -71,7 +71,7 @@
  */
 public class ZygoteProcess {
 
-    private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 20000;
+    private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 60000;
 
     /**
      * Use a relatively short delay, because for app zygote, this is in the critical path of
diff --git a/core/java/android/os/incremental/OWNERS b/core/java/android/os/incremental/OWNERS
index 47eee64..a925564 100644
--- a/core/java/android/os/incremental/OWNERS
+++ b/core/java/android/os/incremental/OWNERS
@@ -1,6 +1,4 @@
 # Bug component: 554432
-alexbuy@google.com
-schfan@google.com
-toddke@google.com
+include /PACKAGE_MANAGER_OWNERS
+
 zyy@google.com
-patb@google.com
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 3ecf74e..54ed73c 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -134,16 +134,16 @@
     @EnforcePermission("MOUNT_UNMOUNT_FILESYSTEMS")
     void setDebugFlags(int flags, int mask) = 60;
     @EnforcePermission("STORAGE_INTERNAL")
-    void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) = 61;
+    void createUserStorageKeys(int userId, boolean ephemeral) = 61;
     @EnforcePermission("STORAGE_INTERNAL")
     void destroyUserStorageKeys(int userId) = 62;
     @EnforcePermission("STORAGE_INTERNAL")
-    void unlockCeStorage(int userId, int serialNumber, in byte[] secret) = 63;
+    void unlockCeStorage(int userId, in byte[] secret) = 63;
     @EnforcePermission("STORAGE_INTERNAL")
     void lockCeStorage(int userId) = 64;
     boolean isCeStorageUnlocked(int userId) = 65;
     @EnforcePermission("STORAGE_INTERNAL")
-    void prepareUserStorage(in String volumeUuid, int userId, int serialNumber, int flags) = 66;
+    void prepareUserStorage(in String volumeUuid, int userId, int flags) = 66;
     @EnforcePermission("STORAGE_INTERNAL")
     void destroyUserStorage(in String volumeUuid, int userId, int flags) = 67;
     @EnforcePermission("STORAGE_INTERNAL")
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 4b9bd47..4c3f330 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1602,14 +1602,13 @@
      * This is only intended to be called by UserManagerService, as part of creating a user.
      *
      * @param userId ID of the user
-     * @param serialNumber serial number of the user
      * @param ephemeral whether the user is ephemeral
      * @throws RuntimeException on error.  The user's keys already existing is considered an error.
      * @hide
      */
-    public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) {
+    public void createUserStorageKeys(int userId, boolean ephemeral) {
         try {
-            mStorageManager.createUserStorageKeys(userId, serialNumber, ephemeral);
+            mStorageManager.createUserStorageKeys(userId, ephemeral);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1653,9 +1652,9 @@
     }
 
     /** {@hide} */
-    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
+    public void prepareUserStorage(String volumeUuid, int userId, int flags) {
         try {
-            mStorageManager.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+            mStorageManager.prepareUserStorage(volumeUuid, userId, flags);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1670,12 +1669,6 @@
         }
     }
 
-    /** {@hide} */
-    @TestApi
-    public static boolean isUserKeyUnlocked(int userId) {
-        return isCeStorageUnlocked(userId);
-    }
-
     /**
      * Returns true if the user's credential-encrypted (CE) storage is unlocked.
      *
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2cc850d..7bad9c5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12961,6 +12961,16 @@
         @Readable
         public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
 
+
+        /**
+         * Whether to boot with 16K page size compatible kernel
+         * 1 = Boot with 16K kernel
+         * 0 = Boot with 4K kernel (default)
+         * @hide
+         */
+        @Readable
+        public static final String ENABLE_16K_PAGES = "enable_16k_pages";
+
         /** Timeout for package verification.
         * @hide */
         @Readable
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 59b945c..db48bad 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -366,11 +366,13 @@
          * <p>
          * As of Android 11 apps will need specific permission to query other packages. To use
          * this method an app must include in their AndroidManifest:
+         * <pre>{@code
          * <queries>
          *   <intent>
          *     <action android:name="android.provider.Telephony.SMS_DELIVER"/>
          *   </intent>
          * </queries>
+         * }</pre>
          * Which will allow them to query packages which declare intent filters that include
          * the {@link android.provider.Telephony.Sms.Intents#SMS_DELIVER_ACTION} intent.
          * </p>
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 533d459..8bd6c85 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -1,7 +1,7 @@
 # Bug component: 36824
 
 brambonne@google.com
-brufino@google.com
+eranm@google.com
 jeffv@google.com
 
 per-file *NetworkSecurityPolicy.java = file:net/OWNERS
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 30524a1..43163b3 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -53,8 +53,15 @@
 
 flag {
     name: "frp_enforcement"
-    namespace: "android_hw_security"
+    namespace: "hardware_backed_security"
     description: "This flag controls whether PDB enforces FRP"
     bug: "290312729"
     is_fixed_read_only: true
 }
+
+flag {
+  name: "report_primary_auth_attempts"
+  namespace: "biometrics"
+  description: "Report primary auth attempts from LockSettingsService"
+  bug: "285053096"
+}
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
new file mode 100644
index 0000000..0bae459
--- /dev/null
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -0,0 +1,43 @@
+package: "android.security"
+
+flag {
+    name: "extend_ecm_to_all_settings"
+    namespace: "responsible_apis"
+    description: "Allow all app settings to be restrictable via configuration"
+    bug: "297372999"
+}
+
+flag {
+    name: "asm_restrictions_enabled"
+    namespace: "responsible_apis"
+    description: "Enables ASM restrictions for activity starts and finishes"
+    bug: "230590090"
+}
+
+flag {
+    name: "asm_toasts_enabled"
+    namespace: "responsible_apis"
+    description: "Enables toasts when ASM restrictions are triggered"
+    bug: "230590090"
+}
+
+flag {
+    name: "content_uri_permission_apis"
+    namespace: "responsible_apis"
+    description: "Enables the content URI permission APIs"
+    bug: "293467489"
+}
+
+flag {
+    name: "enforce_intent_filter_match"
+    namespace: "responsible_apis"
+    description: "Make delivered intents match components' intent filters"
+    bug: "293560872"
+}
+
+flag {
+    name: "block_null_action_intents"
+    namespace: "responsible_apis"
+    description: "Do not allow intents without an action to match any intent filters"
+    bug: "293560872"
+}
diff --git a/core/java/android/service/contextualsearch/OWNERS b/core/java/android/service/contextualsearch/OWNERS
new file mode 100644
index 0000000..463adf4
--- /dev/null
+++ b/core/java/android/service/contextualsearch/OWNERS
@@ -0,0 +1,3 @@
+srazdan@google.com
+volnov@google.com
+hackz@google.com
diff --git a/core/java/android/service/wearable/OWNERS b/core/java/android/service/wearable/OWNERS
index 073e2d7..eca48b7 100644
--- a/core/java/android/service/wearable/OWNERS
+++ b/core/java/android/service/wearable/OWNERS
@@ -1,3 +1 @@
-charliewang@google.com
-oni@google.com
-volnov@google.com
\ No newline at end of file
+include /core/java/android/app/wearable/OWNERS
\ No newline at end of file
diff --git a/core/java/android/view/MotionPredictor.java b/core/java/android/view/MotionPredictor.java
index 27af300..db2efaa 100644
--- a/core/java/android/view/MotionPredictor.java
+++ b/core/java/android/view/MotionPredictor.java
@@ -20,6 +20,8 @@
 import android.annotation.Nullable;
 import android.content.Context;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import libcore.util.NativeAllocationRegistry;
 
 /**
@@ -57,11 +59,21 @@
      * @param context The context for the predictions
      */
     public MotionPredictor(@NonNull Context context) {
-        mIsPredictionEnabled = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableMotionPrediction);
-        final int offsetNanos = context.getResources().getInteger(
-                com.android.internal.R.integer.config_motionPredictionOffsetNanos);
-        mPtr = nativeInitialize(offsetNanos);
+        this(
+                context.getResources().getBoolean(
+                        com.android.internal.R.bool.config_enableMotionPrediction),
+                context.getResources().getInteger(
+                        com.android.internal.R.integer.config_motionPredictionOffsetNanos));
+    }
+
+    /**
+     * Internal constructor for testing.
+     * @hide
+     */
+    @VisibleForTesting
+    public MotionPredictor(boolean isPredictionEnabled, int motionPredictionOffsetNanos) {
+        mIsPredictionEnabled = isPredictionEnabled;
+        mPtr = nativeInitialize(motionPredictionOffsetNanos);
         RegistryHolder.REGISTRY.registerNativeAllocation(this, mPtr);
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cd2d36c..72d4cda 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2191,7 +2191,8 @@
         } else {
             mDisplay = preferredDisplay;
         }
-        if (mHdrSdrRatioChangedListener != null && mDisplay != null) {
+        if (mHdrSdrRatioChangedListener != null && mDisplay != null
+                && mDisplay.isHdrSdrRatioAvailable()) {
             mDisplay.registerHdrSdrRatioChangedListener(mExecutor, mHdrSdrRatioChangedListener);
         }
         mContext.updateDisplay(mDisplay.getDisplayId());
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index 7694754..c9ffdae 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -49,7 +49,6 @@
 import android.view.View;
 import android.view.ViewRootImpl;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -60,6 +59,7 @@
 import java.lang.ref.WeakReference;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
@@ -164,18 +164,15 @@
         boolean cancellable();
     }
 
-    @GuardedBy("mLock")
-    @Nullable
-    private InputConnection mInputConnection;
+    @NonNull
+    private final AtomicReference<InputConnection> mInputConnectionRef;
+    @NonNull
+    private final AtomicBoolean mDeactivateRequested = new AtomicBoolean(false);
 
     @NonNull
     private final Looper mLooper;
     private final Handler mH;
 
-    private final Object mLock = new Object();
-    @GuardedBy("mLock")
-    private boolean mFinished = false;
-
     private final InputMethodManager mParentInputMethodManager;
     private final WeakReference<View> mServedView;
 
@@ -193,7 +190,7 @@
     RemoteInputConnectionImpl(@NonNull Looper looper,
             @NonNull InputConnection inputConnection,
             @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) {
-        mInputConnection = inputConnection;
+        mInputConnectionRef = new AtomicReference<>(inputConnection);
         mLooper = looper;
         mH = new Handler(mLooper);
         mParentInputMethodManager = inputMethodManager;
@@ -211,9 +208,7 @@
      */
     @Nullable
     public InputConnection getInputConnection() {
-        synchronized (mLock) {
-            return mInputConnection;
-        }
+        return mInputConnectionRef.get();
     }
 
     /**
@@ -229,13 +224,7 @@
      * {@link InputConnection#closeConnection()} as a result of {@link #deactivate()}.
      */
     private boolean isFinished() {
-        synchronized (mLock) {
-            return mFinished;
-        }
-    }
-
-    private boolean isActive() {
-        return mParentInputMethodManager.isActive() && !isFinished();
+        return mInputConnectionRef.get() == null;
     }
 
     private View getServedView() {
@@ -372,28 +361,18 @@
      */
     @Dispatching(cancellable = false)
     public void deactivate() {
-        if (isFinished()) {
+        if (mDeactivateRequested.getAndSet(true)) {
             // This is a small performance optimization.  Still only the 1st call of
-            // reportFinish() will take effect.
+            // deactivate() will take effect.
             return;
         }
         dispatch(() -> {
             // Deactivate the notifier when finishing typing.
             notifyTypingHint(false /* isTyping */, true /* deactivate */);
 
-            // Note that we do not need to worry about race condition here, because 1) mFinished is
-            // updated only inside this block, and 2) the code here is running on a Handler hence we
-            // assume multiple closeConnection() tasks will not be handled at the same time.
-            if (isFinished()) {
-                return;
-            }
             Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#closeConnection");
             try {
                 InputConnection ic = getInputConnection();
-                // Note we do NOT check isActive() here, because this is safe
-                // for an IME to call at any time, and we need to allow it
-                // through to clean up our state after the IME has switched to
-                // another client.
                 if (ic == null) {
                     return;
                 }
@@ -403,10 +382,7 @@
                     // TODO(b/199934664): See if we can remove this by providing a default impl.
                 }
             } finally {
-                synchronized (mLock) {
-                    mInputConnection = null;
-                    mFinished = true;
-                }
+                mInputConnectionRef.set(null);
                 Trace.traceEnd(Trace.TRACE_TAG_INPUT);
             }
 
@@ -460,8 +436,7 @@
     public String toString() {
         return "RemoteInputConnectionImpl{"
                 + "connection=" + getInputConnection()
-                + " finished=" + isFinished()
-                + " mParentInputMethodManager.isActive()=" + mParentInputMethodManager.isActive()
+                + " mDeactivateRequested=" + mDeactivateRequested.get()
                 + " mServedView=" + mServedView.get()
                 + "}";
     }
@@ -474,16 +449,14 @@
      *                {@link DumpableInputConnection#dumpDebug(ProtoOutputStream, long)}.
      */
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        synchronized (mLock) {
-            // Check that the call is initiated in the target thread of the current InputConnection
-            // {@link InputConnection#getHandler} since the messages to IInputConnectionWrapper are
-            // executed on this thread. Otherwise the messages are dispatched to the correct thread
-            // in IInputConnectionWrapper, but this is not wanted while dumpng, for performance
-            // reasons.
-            if ((mInputConnection instanceof DumpableInputConnection)
-                    && mLooper.isCurrentThread()) {
-                ((DumpableInputConnection) mInputConnection).dumpDebug(proto, fieldId);
-            }
+        final InputConnection ic = mInputConnectionRef.get();
+        // Check that the call is initiated in the target thread of the current InputConnection
+        // {@link InputConnection#getHandler} since the messages to IInputConnectionWrapper are
+        // executed on this thread. Otherwise the messages are dispatched to the correct thread
+        // in IInputConnectionWrapper, but this is not wanted while dumping, for performance
+        // reasons.
+        if ((ic instanceof DumpableInputConnection) && mLooper.isCurrentThread()) {
+            ((DumpableInputConnection) ic).dumpDebug(proto, fieldId);
         }
     }
 
@@ -498,7 +471,7 @@
     public void dispatchReportFullscreenMode(boolean enabled) {
         dispatch(() -> {
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 return;
             }
             ic.reportFullscreenMode(enabled);
@@ -514,7 +487,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
                 return null;
             }
@@ -536,7 +509,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
                 return null;
             }
@@ -558,7 +531,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getSelectedText on inactive InputConnection");
                 return null;
             }
@@ -580,7 +553,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getSurroundingText on inactive InputConnection");
                 return null;
             }
@@ -608,7 +581,7 @@
                 return 0;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
                 return 0;
             }
@@ -625,7 +598,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getExtractedText on inactive InputConnection");
                 return null;
             }
@@ -642,7 +615,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitText on inactive InputConnection");
                 return;
             }
@@ -660,7 +633,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitText on inactive InputConnection");
                 return;
             }
@@ -676,7 +649,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitCompletion on inactive InputConnection");
                 return;
             }
@@ -692,7 +665,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitCorrection on inactive InputConnection");
                 return;
             }
@@ -712,7 +685,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setSelection on inactive InputConnection");
                 return;
             }
@@ -728,7 +701,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performEditorAction on inactive InputConnection");
                 return;
             }
@@ -744,7 +717,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performContextMenuAction on inactive InputConnection");
                 return;
             }
@@ -760,7 +733,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingRegion on inactive InputConnection");
                 return;
             }
@@ -781,7 +754,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingRegion on inactive InputConnection");
                 return;
             }
@@ -798,7 +771,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingText on inactive InputConnection");
                 return;
             }
@@ -816,7 +789,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingText on inactive InputConnection");
                 return;
             }
@@ -845,11 +818,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            // Note we do NOT check isActive() here, because this is safe
-            // for an IME to call at any time, and we need to allow it
-            // through to clean up our state after the IME has switched to
-            // another client.
-            if (ic == null) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "finishComposingTextFromImm on inactive InputConnection");
                 return;
             }
@@ -873,11 +842,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            // Note we do NOT check isActive() here, because this is safe
-            // for an IME to call at any time, and we need to allow it
-            // through to clean up our state after the IME has switched to
-            // another client.
-            if (ic == null) {
+            if (ic == null && mDeactivateRequested.get()) {
                 Log.w(TAG, "finishComposingText on inactive InputConnection");
                 return;
             }
@@ -893,7 +858,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "sendKeyEvent on inactive InputConnection");
                 return;
             }
@@ -909,7 +874,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
                 return;
             }
@@ -926,7 +891,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
                 return;
             }
@@ -944,7 +909,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection");
                 return;
             }
@@ -964,7 +929,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "beginBatchEdit on inactive InputConnection");
                 return;
             }
@@ -980,7 +945,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "endBatchEdit on inactive InputConnection");
                 return;
             }
@@ -996,7 +961,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performSpellCheck on inactive InputConnection");
                 return;
             }
@@ -1013,7 +978,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performPrivateCommand on inactive InputConnection");
                 return;
             }
@@ -1051,7 +1016,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performHandwritingGesture on inactive InputConnection");
                 if (resultReceiver != null) {
                     resultReceiver.send(
@@ -1091,7 +1056,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "previewHandwritingGesture on inactive InputConnection");
                 return; // cancelled
             }
@@ -1139,7 +1104,7 @@
             @InputConnection.CursorUpdateMode int cursorUpdateMode,
             @InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) {
         final InputConnection ic = getInputConnection();
-        if (ic == null || !isActive()) {
+        if (ic == null || mDeactivateRequested.get()) {
             Log.w(TAG, "requestCursorUpdates on inactive InputConnection");
             return false;
         }
@@ -1177,7 +1142,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "requestTextBoundsInfo on inactive InputConnection");
                 resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
                 return;
@@ -1221,7 +1186,7 @@
                 return false;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitContent on inactive InputConnection");
                 return false;
             }
@@ -1246,7 +1211,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setImeConsumesInput on inactive InputConnection");
                 return;
             }
@@ -1270,7 +1235,7 @@
                         return; // cancelled
                     }
                     InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
+                    if (ic == null || mDeactivateRequested.get()) {
                         Log.w(TAG, "replaceText on inactive InputConnection");
                         return;
                     }
@@ -1289,7 +1254,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "commitText on inactive InputConnection");
                     return;
                 }
@@ -1309,7 +1274,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "setSelection on inactive InputConnection");
                     return;
                 }
@@ -1326,7 +1291,7 @@
                     return null;  // cancelled
                 }
                 final InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "getSurroundingText on inactive InputConnection");
                     return null;
                 }
@@ -1354,7 +1319,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
                     return;
                 }
@@ -1370,7 +1335,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "sendKeyEvent on inactive InputConnection");
                     return;
                 }
@@ -1386,7 +1351,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "performEditorAction on inactive InputConnection");
                     return;
                 }
@@ -1402,7 +1367,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "performContextMenuAction on inactive InputConnection");
                     return;
                 }
@@ -1419,7 +1384,7 @@
                     return 0;  // cancelled
                 }
                 final InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
                     return 0;
                 }
@@ -1435,7 +1400,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
                     return;
                 }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6ad1960..2bc584a 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -423,12 +423,12 @@
     int mMotionViewNewTop;
 
     /**
-     * The X value associated with the the down motion event
+     * The X value associated with the down motion event
      */
     int mMotionX;
 
     /**
-     * The Y value associated with the the down motion event
+     * The Y value associated with the down motion event
      */
     @UnsupportedAppUsage
     int mMotionY;
@@ -7346,7 +7346,7 @@
 
             scrap.dispatchStartTemporaryDetach();
 
-            // The the accessibility state of the view may change while temporary
+            // the accessibility state of the view may change while temporary
             // detached and we do not allow detached views to fire accessibility
             // events. So we are announcing that the subtree changed giving a chance
             // to clients holding on to a view in this subtree to refresh it.
@@ -7715,7 +7715,7 @@
     }
 
     /**
-     * Abstract positon scroller used to handle smooth scrolling.
+     * Abstract position scroller used to handle smooth scrolling.
      */
     static abstract class AbsPositionScroller {
         public abstract void start(int position);
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 76e97ad..3b7e1e9 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -170,7 +170,7 @@
      * @see android.view.View#measure(int, int)
      *
      * Figure out the dimensions of this Spinner. The width comes from
-     * the widthMeasureSpec as Spinnners can't have their width set to
+     * the widthMeasureSpec as Spinners can't have their width set to
      * UNSPECIFIED. The height is based on the height of the selected item
      * plus padding.
      */
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index e20357fa..1dc90ed 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -15,3 +15,5 @@
 per-file Remote* = file:../appwidget/OWNERS
 
 per-file Toast.java = juliacr@google.com, jeffdq@google.com
+
+per-file flags/notification_widget_flags.aconfig = juliacr@google.com, jeffdq@google.com
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index bdaad2b..473b814 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -47,6 +47,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
+import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -337,7 +338,14 @@
                         "SplashScreenView");
                 ImageView imageView = new ImageView(viewContext);
                 imageView.setBackground(mIconDrawable);
-                viewHost.setView(imageView, mIconSize, mIconSize);
+                final int windowFlag = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+                final WindowManager.LayoutParams lp =
+                        new WindowManager.LayoutParams(mIconSize, mIconSize,
+                                WindowManager.LayoutParams.TYPE_APPLICATION, windowFlag,
+                                PixelFormat.TRANSPARENT);
+                viewHost.setView(imageView, lp);
                 SurfaceControlViewHost.SurfacePackage surfacePackage = viewHost.getSurfacePackage();
                 surfaceView.setChildSurfacePackage(surfacePackage);
                 view.mSurfacePackage = surfacePackage;
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
index 8fc4c30..7f8c8a1 100644
--- a/core/java/com/android/internal/util/RingBuffer.java
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -19,7 +19,10 @@
 import static com.android.internal.util.Preconditions.checkArgumentPositive;
 
 import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
 import java.util.Arrays;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
 
 /**
  * A simple ring buffer structure with bounded capacity backed by an array.
@@ -29,16 +32,35 @@
  */
 public class RingBuffer<T> {
 
+    private final Supplier<T> mNewItem;
     // Array for storing events.
     private final T[] mBuffer;
     // Cursor keeping track of the logical end of the array. This cursor never
     // wraps and instead keeps track of the total number of append() operations.
     private long mCursor = 0;
 
+    /**
+     * @deprecated This uses reflection to create new instances.
+     *             Use {@link #RingBuffer(Supplier, IntFunction, int)}} instead.
+     */
+    @Deprecated
     public RingBuffer(Class<T> c, int capacity) {
+        this(() -> (T) createNewItem(c), cap -> (T[]) Array.newInstance(c, cap), capacity);
+    }
+
+    private static Object createNewItem(Class c) {
+        try {
+            return c.getDeclaredConstructor().newInstance();
+        } catch (IllegalAccessException | InstantiationException | NoSuchMethodException
+                 | InvocationTargetException e) {
+            return null;
+        }
+    }
+
+    public RingBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) {
         checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
-        // Java cannot create generic arrays without a runtime hint.
-        mBuffer = (T[]) Array.newInstance(c, capacity);
+        mBuffer = newBacking.apply(capacity);
+        mNewItem = newItem;
     }
 
     public int size() {
@@ -68,22 +90,11 @@
     public T getNextSlot() {
         final int nextSlotIdx = indexOf(mCursor++);
         if (mBuffer[nextSlotIdx] == null) {
-            mBuffer[nextSlotIdx] = createNewItem();
+            mBuffer[nextSlotIdx] = mNewItem.get();
         }
         return mBuffer[nextSlotIdx];
     }
 
-    /**
-     * @return a new object of type <T> or null if a new object could not be created.
-     */
-    protected T createNewItem() {
-        try {
-            return (T) mBuffer.getClass().getComponentType().newInstance();
-        } catch (IllegalAccessException | InstantiationException e) {
-            return null;
-        }
-    }
-
     public T[] toArray() {
         // Only generic way to create a T[] from another T[]
         T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 8114e1f..e591327 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -166,4 +166,16 @@
      * Refreshes pending strong auth timeout with the latest admin requirement set by device policy.
      */
     public abstract void refreshStrongAuthTimeout(int userId);
+
+    /**
+     * Register a LockSettingsStateListener
+     * @param listener The listener to be registered
+     */
+    public abstract void registerLockSettingsStateListener(LockSettingsStateListener listener);
+
+    /**
+     * Unregister a LockSettingsStateListener
+     * @param listener The listener to be unregistered
+     */
+    public abstract void unregisterLockSettingsStateListener(LockSettingsStateListener listener);
 }
diff --git a/core/java/com/android/internal/widget/LockSettingsStateListener.java b/core/java/com/android/internal/widget/LockSettingsStateListener.java
new file mode 100644
index 0000000..869e676
--- /dev/null
+++ b/core/java/com/android/internal/widget/LockSettingsStateListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+/**
+ * Callback interface between LockSettingService and other system services to be notified about the
+ * state of primary authentication (i.e. PIN/pattern/password).
+ * @hide
+ */
+public interface LockSettingsStateListener {
+    /**
+     * Defines behavior in response to a successful authentication
+     * @param userId The user Id for the requested authentication
+     */
+    void onAuthenticationSucceeded(int userId);
+
+    /**
+     * Defines behavior in response to a failed authentication
+     * @param userId The user Id for the requested authentication
+     */
+    void onAuthenticationFailed(int userId);
+}
diff --git a/core/java/com/android/server/OWNERS b/core/java/com/android/server/OWNERS
deleted file mode 100644
index 1c2d19d..0000000
--- a/core/java/com/android/server/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file SystemConfig.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f5b12db..c4ff402 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -182,8 +182,6 @@
                 "android_os_SharedMemory.cpp",
                 "android_os_storage_StorageManager.cpp",
                 "android_os_UEventObserver.cpp",
-                "android_os_VintfObject.cpp",
-                "android_os_VintfRuntimeInfo.cpp",
                 "android_os_incremental_IncrementalManager.cpp",
                 "android_net_LocalSocketImpl.cpp",
                 "android_service_DataLoaderService.cpp",
@@ -271,6 +269,7 @@
                 "libdmabufinfo",
                 "libgif",
                 "libgui_window_info_static",
+                "libkernelconfigs",
                 "libseccomp_policy",
                 "libgrallocusage",
                 "libscrypt_static",
@@ -340,7 +339,6 @@
                 "libnativeloader_lazy",
                 "libmemunreachable",
                 "libhidlbase",
-                "libvintf",
                 "libnativedisplay",
                 "libnativewindow",
                 "libdl",
@@ -448,8 +446,25 @@
                 // (e.g. gDefaultServiceManager)
                 "libbinder",
                 "libhidlbase", // libhwbinder is in here
-                "libvintf",
             ],
         },
     },
 }
+
+cc_library_shared {
+    name: "libvintf_jni",
+
+    cpp_std: "gnu++20",
+
+    srcs: [
+        "android_os_VintfObject.cpp",
+        "android_os_VintfRuntimeInfo.cpp",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libnativehelper",
+        "libvintf",
+    ],
+}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 08fb51b..a3bc2bb 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -151,8 +151,6 @@
 extern int register_android_os_Parcel(JNIEnv* env);
 extern int register_android_os_PerformanceHintManager(JNIEnv* env);
 extern int register_android_os_SELinux(JNIEnv* env);
-extern int register_android_os_VintfObject(JNIEnv *env);
-extern int register_android_os_VintfRuntimeInfo(JNIEnv *env);
 extern int register_android_os_storage_StorageManager(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv *env);
 extern int register_android_os_SystemClock(JNIEnv* env);
@@ -1541,8 +1539,6 @@
         REG_JNI(register_android_os_NativeHandle),
         REG_JNI(register_android_os_ServiceManager),
         REG_JNI(register_android_os_storage_StorageManager),
-        REG_JNI(register_android_os_VintfObject),
-        REG_JNI(register_android_os_VintfRuntimeInfo),
         REG_JNI(register_android_service_DataLoaderService),
         REG_JNI(register_android_view_DisplayEventReceiver),
         REG_JNI(register_android_view_Surface),
diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp
index 2f29cae..917d283 100644
--- a/core/jni/android_opengl_EGL14.cpp
+++ b/core/jni/android_opengl_EGL14.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/core/jni/android_opengl_EGL15.cpp b/core/jni/android_opengl_EGL15.cpp
index b9c36b9..447b8ec 100644
--- a/core/jni/android_opengl_EGL15.cpp
+++ b/core/jni/android_opengl_EGL15.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/core/jni/android_opengl_EGLExt.cpp b/core/jni/android_opengl_EGLExt.cpp
index cdc9852..ffd75ea 100644
--- a/core/jni/android_opengl_EGLExt.cpp
+++ b/core/jni/android_opengl_EGLExt.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
@@ -54,11 +55,11 @@
     jclass eglsurfaceClassLocal = _env->FindClass("android/opengl/EGLSurface");
     eglsurfaceClass = (jclass) _env->NewGlobalRef(eglsurfaceClassLocal);
     jclass eglsyncClassLocal = _env->FindClass("android/opengl/EGLSync");
-    eglsyncClass = (jclass)_env->NewGlobalRef(eglsyncClassLocal);
+    eglsyncClass = (jclass) _env->NewGlobalRef(eglsyncClassLocal);
 
     egldisplayGetHandleID = _env->GetMethodID(egldisplayClass, "getNativeHandle", "()J");
     eglsurfaceGetHandleID = _env->GetMethodID(eglsurfaceClass, "getNativeHandle", "()J");
-    eglsyncGetHandleID = _env->GetMethodID(eglsyncClassLocal, "getNativeHandle", "()J");
+    eglsyncGetHandleID = _env->GetMethodID(eglsyncClass, "getNativeHandle", "()J");
 }
 
 static void *
@@ -72,6 +73,14 @@
     return reinterpret_cast<void*>(_env->CallLongMethod(obj, mid));
 }
 
+// TODO: this should be generated from the .spec file, but needs to be renamed and made private
+static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
+    EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
+    EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
+
+    return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
+}
+
 // --------------------------------------------------------------------------
 /* EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time ) */
 static jboolean
@@ -89,21 +98,12 @@
     return (jboolean)_returnValue;
 }
 
-static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
-    EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
-    EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
-
-    return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
-}
-
 static const char *classPathName = "android/opengl/EGLExt";
 
 static const JNINativeMethod methods[] = {
-        {"_nativeClassInit", "()V", (void *)nativeClassInit},
-        {"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z",
-         (void *)android_eglPresentationTimeANDROID},
-        {"eglDupNativeFenceFDANDROIDImpl", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSync;)I",
-         (void *)android_eglDupNativeFenceFDANDROID},
+{"_nativeClassInit", "()V", (void*)nativeClassInit },
+{"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z", (void *) android_eglPresentationTimeANDROID },
+{"eglDupNativeFenceFDANDROIDImpl", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSync;)I", (void *)android_eglDupNativeFenceFDANDROID },
 };
 
 int register_android_opengl_jni_EGLExt(JNIEnv *_env)
diff --git a/core/jni/android_opengl_GLES10.cpp b/core/jni/android_opengl_GLES10.cpp
index d65b498..2d921ad 100644
--- a/core/jni/android_opengl_GLES10.cpp
+++ b/core/jni/android_opengl_GLES10.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES10Ext.cpp b/core/jni/android_opengl_GLES10Ext.cpp
index 3638b87..35a9a68 100644
--- a/core/jni/android_opengl_GLES10Ext.cpp
+++ b/core/jni/android_opengl_GLES10Ext.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES11.cpp b/core/jni/android_opengl_GLES11.cpp
index 9724e6c..e04b56e 100644
--- a/core/jni/android_opengl_GLES11.cpp
+++ b/core/jni/android_opengl_GLES11.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES11Ext.cpp b/core/jni/android_opengl_GLES11Ext.cpp
index 1ffa4ec..bccbda6 100644
--- a/core/jni/android_opengl_GLES11Ext.cpp
+++ b/core/jni/android_opengl_GLES11Ext.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES/gl.h>
diff --git a/core/jni/android_opengl_GLES20.cpp b/core/jni/android_opengl_GLES20.cpp
index d832558..165262e 100644
--- a/core/jni/android_opengl_GLES20.cpp
+++ b/core/jni/android_opengl_GLES20.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES2/gl2.h>
diff --git a/core/jni/android_opengl_GLES30.cpp b/core/jni/android_opengl_GLES30.cpp
index 719c6b3..d3fe439 100644
--- a/core/jni/android_opengl_GLES30.cpp
+++ b/core/jni/android_opengl_GLES30.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES3/gl3.h>
diff --git a/core/jni/android_opengl_GLES31.cpp b/core/jni/android_opengl_GLES31.cpp
index afe7c63..b123f9d 100644
--- a/core/jni/android_opengl_GLES31.cpp
+++ b/core/jni/android_opengl_GLES31.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <stdint.h>
diff --git a/core/jni/android_opengl_GLES31Ext.cpp b/core/jni/android_opengl_GLES31Ext.cpp
index 8127433..1e4049b 100644
--- a/core/jni/android_opengl_GLES31Ext.cpp
+++ b/core/jni/android_opengl_GLES31Ext.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <GLES3/gl31.h>
diff --git a/core/jni/android_opengl_GLES32.cpp b/core/jni/android_opengl_GLES32.cpp
index 7ed7548..e0175f0 100644
--- a/core/jni/android_opengl_GLES32.cpp
+++ b/core/jni/android_opengl_GLES32.cpp
@@ -17,6 +17,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include <stdint.h>
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index fe95762..e4b0f1a 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -52,7 +52,7 @@
 #include <memunreachable/memunreachable.h>
 #include <android-base/strings.h>
 #include "android_os_Debug.h"
-#include <vintf/VintfObject.h>
+#include <vintf/KernelConfigs.h>
 
 namespace android
 {
@@ -959,10 +959,9 @@
     } cfg_state = CONFIG_UNKNOWN;
 
     if (cfg_state == CONFIG_UNKNOWN) {
-        auto runtime_info = vintf::VintfObject::GetInstance()->getRuntimeInfo(
-                vintf::RuntimeInfo::FetchFlag::CONFIG_GZ);
-        CHECK(runtime_info != nullptr) << "Kernel configs cannot be fetched. b/151092221";
-        const std::map<std::string, std::string>& configs = runtime_info->kernelConfigs();
+        std::map<std::string, std::string> configs;
+        const status_t result = android::kernelconfigs::LoadKernelConfigs(&configs);
+        CHECK(result == OK) << "Kernel configs could not be fetched. b/151092221";
         std::map<std::string, std::string>::const_iterator it = configs.find("CONFIG_VMAP_STACK");
         cfg_state = (it != configs.end() && it->second == "y") ? CONFIG_SET : CONFIG_UNSET;
     }
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 477bd09..734b5f4 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -39,7 +39,6 @@
 #include <hwbinder/ProcessState.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <vintf/parse_string.h>
 #include <utils/misc.h>
 
 #include "core_jni_helpers.h"
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 1baea2a..ce4a337 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -17,16 +17,14 @@
 #define LOG_TAG "VintfObject"
 //#define LOG_NDEBUG 0
 #include <android-base/logging.h>
-
-#include <vector>
-#include <string>
-
-#include <nativehelper/JNIHelp.h>
 #include <vintf/VintfObject.h>
 #include <vintf/parse_string.h>
 #include <vintf/parse_xml.h>
 
-#include "core_jni_helpers.h"
+#include <vector>
+#include <string>
+
+#include "jni_wrappers.h"
 
 static jclass gString;
 static jclass gHashMapClazz;
@@ -46,6 +44,7 @@
 using vintf::Version;
 using vintf::VintfObject;
 using vintf::Vndk;
+using vintf::CheckFlags::ENABLE_ALL_CHECKS;
 
 template<typename V>
 static inline jobjectArray toJavaStringArray(JNIEnv* env, const V& v) {
@@ -93,12 +92,16 @@
     return toJavaStringArray(env, cStrings);
 }
 
-static jint android_os_VintfObject_verifyWithoutAvb(JNIEnv* env, jclass) {
+static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv*, jclass) {
     std::string error;
-    int32_t status = VintfObject::GetInstance()->checkCompatibility(&error,
-            ::android::vintf::CheckFlags::DISABLE_AVB_CHECK);
+    // Use temporary VintfObject, not the shared instance, to release memory
+    // after check.
+    int32_t status =
+            VintfObject::Builder()
+                    .build()
+                    ->checkCompatibility(&error, ENABLE_ALL_CHECKS.disableAvb().disableKernel());
     if (status)
-        LOG(WARNING) << "VintfObject.verifyWithoutAvb() returns " << status << ": " << error;
+        LOG(WARNING) << "VintfObject.verifyBuildAtBoot() returns " << status << ": " << error;
     return status;
 }
 
@@ -170,7 +173,7 @@
 
 static const JNINativeMethod gVintfObjectMethods[] = {
         {"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
-        {"verifyWithoutAvb", "()I", (void*)android_os_VintfObject_verifyWithoutAvb},
+        {"verifyBuildAtBoot", "()I", (void*)android_os_VintfObject_verifyBuildAtBoot},
         {"getHalNamesAndVersions", "()[Ljava/lang/String;",
          (void*)android_os_VintfObject_getHalNamesAndVersions},
         {"getSepolicyVersion", "()Ljava/lang/String;",
@@ -199,4 +202,23 @@
             NELEM(gVintfObjectMethods));
 }
 
-};
+extern int register_android_os_VintfRuntimeInfo(JNIEnv* env);
+
+} // namespace android
+
+jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+    JNIEnv* env = NULL;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) {
+        return JNI_ERR;
+    }
+
+    if (android::register_android_os_VintfObject(env) < 0) {
+        return JNI_ERR;
+    }
+
+    if (android::register_android_os_VintfRuntimeInfo(env) < 0) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_6;
+}
diff --git a/core/jni/android_os_VintfRuntimeInfo.cpp b/core/jni/android_os_VintfRuntimeInfo.cpp
index b0271b9..7c2f588 100644
--- a/core/jni/android_os_VintfRuntimeInfo.cpp
+++ b/core/jni/android_os_VintfRuntimeInfo.cpp
@@ -17,23 +17,22 @@
 #define LOG_TAG "VintfRuntimeInfo"
 //#define LOG_NDEBUG 0
 
-#include <nativehelper/JNIHelp.h>
 #include <vintf/VintfObject.h>
 #include <vintf/parse_string.h>
 #include <vintf/parse_xml.h>
 
-#include "core_jni_helpers.h"
+#include "jni_wrappers.h"
 
 namespace android {
 
 using vintf::RuntimeInfo;
 using vintf::VintfObject;
 
-#define MAP_STRING_METHOD(javaMethod, cppString, flags)                                  \
-    static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass clazz) { \
-        std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(flags);    \
-        if (info == nullptr) return nullptr;                                             \
-        return env->NewStringUTF((cppString).c_str());                                   \
+#define MAP_STRING_METHOD(javaMethod, cppString, flags)                               \
+    static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass) {    \
+        std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(flags); \
+        if (info == nullptr) return nullptr;                                          \
+        return env->NewStringUTF((cppString).c_str());                                \
     }
 
 MAP_STRING_METHOD(getCpuInfo, info->cpuInfo(), RuntimeInfo::FetchFlag::CPU_INFO);
@@ -49,9 +48,7 @@
 MAP_STRING_METHOD(getBootVbmetaAvbVersion, vintf::to_string(info->bootVbmetaAvbVersion()),
                   RuntimeInfo::FetchFlag::AVB);
 
-
-static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv *env, jclass clazz)
-{
+static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv*, jclass) {
     std::shared_ptr<const RuntimeInfo> info =
             VintfObject::GetRuntimeInfo(RuntimeInfo::FetchFlag::POLICYVERS);
     if (info == nullptr) return 0;
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 7af69f2..d2e58bb 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -28,6 +28,7 @@
 #include <meminfo/sysmeminfo.h>
 #include <processgroup/processgroup.h>
 #include <processgroup/sched_policy.h>
+#include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 
 #include <algorithm>
@@ -232,6 +233,31 @@
     }
 }
 
+// Look up the user ID of a process in /proc/${pid}/status. The Uid: line is present in
+// /proc/${pid}/status since at least kernel v2.5.
+static int uid_from_pid(int pid)
+{
+    int uid = -1;
+    std::array<char, 64> path;
+    int res = snprintf(path.data(), path.size(), "/proc/%d/status", pid);
+    if (res < 0 || res >= static_cast<int>(path.size())) {
+        DCHECK(false);
+        return uid;
+    }
+    FILE* f = fopen(path.data(), "r");
+    if (!f) {
+        return uid;
+    }
+    char line[256];
+    while (fgets(line, sizeof(line), f)) {
+        if (sscanf(line, "Uid: %d", &uid) == 1) {
+            break;
+        }
+    }
+    fclose(f);
+    return uid;
+}
+
 void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
 {
     ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
@@ -275,7 +301,12 @@
         }
     }
 
-    if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
+    const int uid = uid_from_pid(pid);
+    if (uid < 0) {
+        signalExceptionForGroupError(env, ESRCH, pid);
+        return;
+    }
+    if (!SetProcessProfilesCached(uid, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
         signalExceptionForGroupError(env, errno ? errno : EPERM, pid);
 }
 
@@ -1134,12 +1165,11 @@
 
 static jlongArray android_os_Process_getRss(JNIEnv* env, jobject clazz, jint pid)
 {
-    // total, file, anon, swap
-    jlong rss[4] = {0, 0, 0, 0};
+    // total, file, anon, swap, shmem
+    jlong rss[5] = {0, 0, 0, 0, 0};
     std::string status_path =
             android::base::StringPrintf("/proc/%d/status", pid);
     UniqueFile file = MakeUniqueFile(status_path.c_str(), "re");
-
     char line[256];
     while (file != nullptr && fgets(line, sizeof(line), file.get())) {
         jlong v;
@@ -1151,17 +1181,18 @@
             rss[2] = v;
         } else if ( sscanf(line, "VmSwap: %" SCNd64 " kB", &v) == 1) {
             rss[3] = v;
+        } else if ( sscanf(line, "RssShmem: %" SCNd64 " kB", &v) == 1) {
+            rss[4] = v;
         }
     }
 
-    jlongArray rssArray = env->NewLongArray(4);
+    jlongArray rssArray = env->NewLongArray(5);
     if (rssArray == NULL) {
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
         return NULL;
     }
 
-    env->SetLongArrayRegion(rssArray, 0, 4, rss);
-
+    env->SetLongArrayRegion(rssArray, 0, 5, rss);
     return rssArray;
 }
 
diff --git a/core/jni/com_google_android_gles_jni_GLImpl.cpp b/core/jni/com_google_android_gles_jni_GLImpl.cpp
index 21de723..ef29c88 100644
--- a/core/jni/com_google_android_gles_jni_GLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -18,6 +18,7 @@
 // This source file is automatically generated
 
 #pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
 #pragma GCC diagnostic ignored "-Wunused-function"
 
 #include "jni.h"
diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h
index 210dc89..769fa72 100644
--- a/core/jni/core_jni_helpers.h
+++ b/core/jni/core_jni_helpers.h
@@ -22,6 +22,8 @@
 #include <nativehelper/scoped_utf_chars.h>
 #include <android_runtime/AndroidRuntime.h>
 
+#include "jni_wrappers.h"
+
 // Host targets (layoutlib) do not differentiate between regular and critical native methods,
 // and they need all the JNI methods to have JNIEnv* and jclass/jobject as their first two arguments.
 // The following macro allows to have those arguments when compiling for host while omitting them when
@@ -36,60 +38,6 @@
 
 namespace android {
 
-// Defines some helpful functions.
-
-static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) {
-    jclass clazz = env->FindClass(class_name);
-    LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name);
-    return clazz;
-}
-
-static inline jfieldID GetFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
-                                       const char* field_signature) {
-    jfieldID res = env->GetFieldID(clazz, field_name, field_signature);
-    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find field %s with signature %s", field_name,
-                        field_signature);
-    return res;
-}
-
-static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
-                                         const char* method_signature) {
-    jmethodID res = env->GetMethodID(clazz, method_name, method_signature);
-    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name,
-                        method_signature);
-    return res;
-}
-
-static inline jfieldID GetStaticFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
-                                             const char* field_signature) {
-    jfieldID res = env->GetStaticFieldID(clazz, field_name, field_signature);
-    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s with signature %s", field_name,
-                        field_signature);
-    return res;
-}
-
-static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
-                                               const char* method_signature) {
-    jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature);
-    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s with signature %s",
-                        method_name, method_signature);
-    return res;
-}
-
-template <typename T>
-static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) {
-    jobject res = env->NewGlobalRef(in);
-    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference.");
-    return static_cast<T>(res);
-}
-
-static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
-                                       const JNINativeMethod* gMethods, int numMethods) {
-    int res = AndroidRuntime::registerNativeMethods(env, className, gMethods, numMethods);
-    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
-    return res;
-}
-
 /**
  * Returns the result of invoking java.lang.ref.Reference.get() on a Reference object.
  */
diff --git a/core/jni/hwbinder/EphemeralStorage.cpp b/core/jni/hwbinder/EphemeralStorage.cpp
index 95bb42e..ef0750c 100644
--- a/core/jni/hwbinder/EphemeralStorage.cpp
+++ b/core/jni/hwbinder/EphemeralStorage.cpp
@@ -164,7 +164,7 @@
             }
 
             default:
-                CHECK(!"Should not be here");
+                CHECK(!"Should not be here") << "Item type: " << item.mType;
         }
     }
 
diff --git a/core/jni/jni_wrappers.h b/core/jni/jni_wrappers.h
new file mode 100644
index 0000000..3b29e30
--- /dev/null
+++ b/core/jni/jni_wrappers.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// JNI wrappers for better logging
+
+#include <jni.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static inline jclass FindClassOrDie(JNIEnv* env, const char* class_name) {
+    jclass clazz = env->FindClass(class_name);
+    LOG_ALWAYS_FATAL_IF(clazz == NULL, "Unable to find class %s", class_name);
+    return clazz;
+}
+
+static inline jfieldID GetFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
+                                       const char* field_signature) {
+    jfieldID res = env->GetFieldID(clazz, field_name, field_signature);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find field %s with signature %s", field_name,
+                        field_signature);
+    return res;
+}
+
+static inline jmethodID GetMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
+                                         const char* method_signature) {
+    jmethodID res = env->GetMethodID(clazz, method_name, method_signature);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find method %s with signature %s", method_name,
+                        method_signature);
+    return res;
+}
+
+static inline jfieldID GetStaticFieldIDOrDie(JNIEnv* env, jclass clazz, const char* field_name,
+                                             const char* field_signature) {
+    jfieldID res = env->GetStaticFieldID(clazz, field_name, field_signature);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static field %s with signature %s", field_name,
+                        field_signature);
+    return res;
+}
+
+static inline jmethodID GetStaticMethodIDOrDie(JNIEnv* env, jclass clazz, const char* method_name,
+                                               const char* method_signature) {
+    jmethodID res = env->GetStaticMethodID(clazz, method_name, method_signature);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to find static method %s with signature %s",
+                        method_name, method_signature);
+    return res;
+}
+
+template <typename T>
+static inline T MakeGlobalRefOrDie(JNIEnv* env, T in) {
+    jobject res = env->NewGlobalRef(in);
+    LOG_ALWAYS_FATAL_IF(res == NULL, "Unable to create global reference.");
+    return static_cast<T>(res);
+}
+
+static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
+                                       const JNINativeMethod* gMethods, int numMethods) {
+    int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+    return res;
+}
+
+} // namespace android
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index db391f7..381580b 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -13,11 +13,12 @@
 jjaggi@google.com
 kwekua@google.com
 roosa@google.com
-per-file package_item_info.proto = toddke@google.com,patb@google.com
+per-file package_item_info.proto = file:/PACKAGE_MANAGER_OWNERS
 per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
 per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
 per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
 per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
+per-file android/content/intent.proto = file:/PACKAGE_MANAGER_OWNERS
 
 # Biometrics
 jaggies@google.com
@@ -31,5 +32,3 @@
 
 # Accessibility
 pweaver@google.com
-hongmingjin@google.com
-cbrower@google.com
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bc74f39..f5635f4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3522,6 +3522,13 @@
     <permission android:name="android.permission.MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION"
                 android:protectionLevel="internal|role" />
 
+    <!-- Allows an application to set policy related to <a
+    href="https://www.threadgroup.org">Thread</a> network.
+        @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+    -->
+    <permission android:name="android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK"
+                android:protectionLevel="internal|role" />
+
     <!-- Allows an application to set policy related to windows.
         <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
         required to call APIs protected by this permission on users different to the calling user.
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 3a2e50a..9bb2499 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -34,7 +34,7 @@
          http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ -->
 
     <!-- Arab Emirates -->
-    <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" />
+    <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253" />
 
     <!-- Albania: 5 digits, known short codes listed -->
     <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
@@ -86,7 +86,7 @@
     <shortcode country="cn" premium="1066.*" free="1065.*" />
 
     <!-- Colombia: 1-6 digits (not confirmed) -->
-    <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517" />
+    <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" />
 
     <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
@@ -104,6 +104,12 @@
     <!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers -->
     <shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}|4665" />
 
+    <!-- Dominican Republic: 1-6 digits (standard system default, not country specific) -->
+    <shortcode country="do" pattern="\\d{1,6}" free="912892" />
+
+    <!-- Ecuador: 1-6 digits (standard system default, not country specific) -->
+    <shortcode country="ec" pattern="\\d{1,6}" free="466453" />
+
     <!-- Estonia: short codes 3-5 digits starting with 1, plus premium 7 digit numbers starting with 90, plus EU.
          http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht -->
     <shortcode country="ee" pattern="1\\d{2,4}" premium="90\\d{5}|15330|1701[0-3]" free="116\\d{3}|95034" />
@@ -154,8 +160,8 @@
          http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
     <shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" />
 
-    <!-- Israel: 4 digits, known premium codes listed -->
-    <shortcode country="il" pattern="\\d{4}" premium="4422|4545" />
+    <!-- Israel: 1-5 digits, known premium codes listed -->
+    <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" />
 
     <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU:
          https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
@@ -193,11 +199,14 @@
     <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
 
     <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
-    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" />
+    <shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" />
 
     <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
     <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" />
 
+    <!-- Namibia: 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="na" pattern="\\d{1,5}" free="40005" />
+
     <!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
     <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" />
 
diff --git a/core/tests/BroadcastRadioTests/OWNERS b/core/tests/BroadcastRadioTests/OWNERS
index d2bdd64..51a85e4 100644
--- a/core/tests/BroadcastRadioTests/OWNERS
+++ b/core/tests/BroadcastRadioTests/OWNERS
@@ -1,3 +1,3 @@
 xuweilin@google.com
 oscarazu@google.com
-keunyoung@google.com
+ericjeong@google.com
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
index 43266a5..cb3f99c 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java
@@ -17,6 +17,7 @@
 package android.animation;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.util.PollingCheck;
@@ -343,6 +344,20 @@
     }
 
     @Test
+    public void childAnimatorCancelsDuringUpdate_animatorSetIsEnded() throws Throwable {
+        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                animation.cancel();
+            }
+        });
+        mActivity.runOnUiThread(() -> {
+            mSet1.start();
+            assertFalse(mSet1.isRunning());
+        });
+    }
+
+    @Test
     public void reentrantStart() throws Throwable {
         CountDownLatch latch = new CountDownLatch(3);
         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index a0d8183..d2d7452 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -29,6 +29,7 @@
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
 import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
+import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.F_OK;
 import static android.system.OsConstants.O_APPEND;
 import static android.system.OsConstants.O_CREAT;
@@ -37,6 +38,7 @@
 import static android.system.OsConstants.O_TRUNC;
 import static android.system.OsConstants.O_WRONLY;
 import static android.system.OsConstants.R_OK;
+import static android.system.OsConstants.SOCK_STREAM;
 import static android.system.OsConstants.W_OK;
 import static android.system.OsConstants.X_OK;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -54,6 +56,7 @@
 import android.content.Context;
 import android.os.FileUtils.MemoryPipe;
 import android.provider.DocumentsContract.Document;
+import android.system.Os;
 import android.util.DataUnit;
 
 import androidx.test.InstrumentationRegistry;
@@ -70,6 +73,8 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -77,6 +82,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Random;
+import java.net.InetSocketAddress;
 
 @RunWith(AndroidJUnit4.class)
 public class FileUtilsTest {
@@ -249,6 +255,84 @@
         assertArrayEquals(expected, actual);
     }
 
+    //TODO(ravenwood) Remove the _$noRavenwood suffix and add @RavenwoodIgnore instead
+    @Test
+    public void testCopy_SocketToFile_FileToSocket$noRavenwood() throws Exception {
+        for (int size : DATA_SIZES ) {
+            final File src = new File(mTarget, "src");
+            final File dest = new File(mTarget, "dest");
+            byte[] expected = new byte[size];
+            byte[] actual = new byte[size];
+            new Random().nextBytes(expected);
+
+            // write test data in to src file
+            writeFile(src, expected);
+
+            // start server, get data from client and save to dest file (socket --> file)
+            FileDescriptor srvSocketFd = Os.socket(AF_INET, SOCK_STREAM, 0);
+            Os.bind(srvSocketFd, new InetSocketAddress("localhost", 0));
+            Os.listen(srvSocketFd, 5);
+            InetSocketAddress localSocketAddress = (InetSocketAddress) Os.getsockname(srvSocketFd);
+
+            final Thread srv = new Thread(new Runnable() {
+                public void run() {
+                    try {
+                        InetSocketAddress peerAddress = new InetSocketAddress();
+                        FileDescriptor srvConnFd = Os.accept(srvSocketFd, peerAddress);
+
+                        // read file size
+                        byte[] rcvFileSizeByteArray = new byte[8];
+                        Os.read(srvConnFd, rcvFileSizeByteArray, 0, rcvFileSizeByteArray.length);
+                        long rcvFileSize = 0;
+                        for (int i = 0; i < 8; i++) {
+                            rcvFileSize <<= 8;
+                            rcvFileSize |= (rcvFileSizeByteArray[i] & 0xFF);
+                        }
+
+                        FileOutputStream fileOutputStream = new FileOutputStream(dest);
+                        // copy data from socket to file
+                        FileUtils.copy(srvConnFd, fileOutputStream.getFD(), rcvFileSize, null, null, null);
+
+                        fileOutputStream.close();
+                        Os.close(srvConnFd);
+                        Os.close(srvSocketFd);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+            });
+
+            srv.start();
+
+
+            // start client, get data from dest file and send to server (file --> socket)
+            FileDescriptor clientFd = Os.socket(AF_INET, SOCK_STREAM, 0);
+            Os.connect(clientFd, localSocketAddress.getAddress(), localSocketAddress.getPort());
+
+            FileInputStream fileInputStream = new FileInputStream(src);
+            long sndFileSize = src.length();
+            // send the file size to server
+            byte[] sndFileSizeByteArray = new byte[8];
+            for (int i = 7; i >= 0; i--) {
+                sndFileSizeByteArray[i] = (byte)(sndFileSize & 0xFF);
+                sndFileSize >>= 8;
+            }
+            Os.write(clientFd, sndFileSizeByteArray, 0, sndFileSizeByteArray.length);
+
+            // copy data from file to socket
+            FileUtils.copy(fileInputStream.getFD(), clientFd, src.length(), null, null, null);
+
+            fileInputStream.close();
+            Os.close(clientFd);
+
+            srv.join();
+
+            // read test data from dest file
+            actual = readFile(dest);
+            assertArrayEquals(expected, actual);
+        }
+    }
+
     @Test
     public void testIsFilenameSafe() throws Exception {
         assertTrue(FileUtils.isFilenameSafe(new File("foobar")));
diff --git a/core/tests/nfctests/OWNERS b/core/tests/nfctests/OWNERS
deleted file mode 100644
index 34b095c..0000000
--- a/core/tests/nfctests/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /core/java/android/nfc/OWNERS
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea23aba..245f216 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1,3 +1,5 @@
+include /PACKAGE_MANAGER_OWNERS
+
 alanstokes@google.com
 cbrubaker@google.com
 hackbod@android.com
@@ -6,11 +8,6 @@
 jsharkey@android.com
 jsharkey@google.com
 lorenzo@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
-toddke@android.com
-toddke@google.com
-patb@google.com
 yamasani@google.com
 
 per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index dcc9686..fbe1b8e 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -48,6 +48,7 @@
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
         <permission name="android.permission.REBOOT"/>
+        <permission name="android.permission.RECOVERY"/>
         <permission name="android.permission.STATUS_BAR"/>
         <permission name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"/>
         <permission name="android.permission.TETHER_PRIVILEGED"/>
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 3dd9ba9..f403b4f 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -57,3 +57,9 @@
     name: "font_fallback.xml",
     src: "font_fallback.xml",
 }
+
+/////////////////////////////////
+// Move `fontchain_lint` to `core/tasks/fontchain_lint.mk`.
+// Because `system.img` is a dependency of `fontchain_lint`, it cannot be
+// converted to Android.bp for now.
+// After system.img can be generated by Soong, then it can be converted to Android.bp.
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
deleted file mode 100644
index a322b82..0000000
--- a/data/fonts/Android.mk
+++ /dev/null
@@ -1,45 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-# Run sanity tests on fonts on checkbuild
-checkbuild: fontchain_lint
-
-FONTCHAIN_LINTER := $(HOST_OUT_EXECUTABLES)/fontchain_linter
-ifeq ($(MINIMAL_FONT_FOOTPRINT),true)
-CHECK_EMOJI := false
-else
-CHECK_EMOJI := true
-endif
-
-fontchain_lint_timestamp := $(call intermediates-dir-for,PACKAGING,fontchain_lint)/stamp
-
-.PHONY: fontchain_lint
-fontchain_lint: $(fontchain_lint_timestamp)
-
-fontchain_lint_deps := \
-    external/unicode/DerivedAge.txt \
-    external/unicode/emoji-data.txt \
-    external/unicode/emoji-sequences.txt \
-    external/unicode/emoji-variation-sequences.txt \
-    external/unicode/emoji-zwj-sequences.txt \
-    external/unicode/additions/emoji-data.txt \
-    external/unicode/additions/emoji-sequences.txt \
-    external/unicode/additions/emoji-zwj-sequences.txt \
-
-$(fontchain_lint_timestamp): $(FONTCHAIN_LINTER) $(TARGET_OUT)/etc/fonts.xml $(PRODUCT_OUT)/system.img $(fontchain_lint_deps)
-	@echo Running fontchain lint
-	$(FONTCHAIN_LINTER) $(TARGET_OUT) $(CHECK_EMOJI) external/unicode
-	touch $@
diff --git a/data/keyboards/Android.bp b/data/keyboards/Android.bp
new file mode 100644
index 0000000..f15c153
--- /dev/null
+++ b/data/keyboards/Android.bp
@@ -0,0 +1,29 @@
+// Copyright 2010 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.
+
+genrule {
+    name: "validate_framework_keymaps",
+    srcs: [
+        "*.kl",
+        "*.kcm",
+        "*.idc",
+    ],
+    tools: ["validatekeymaps"],
+    out: ["stamp"],
+    cmd: "$(location validatekeymaps) -q $(in) " +
+        "&& touch $(out)",
+    dist: {
+        targets: ["droidcore"],
+    },
+}
diff --git a/data/keyboards/Android.mk b/data/keyboards/Android.mk
deleted file mode 100644
index 6ae8800..0000000
--- a/data/keyboards/Android.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (C) 2010 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 makefile performs build time validation of framework keymap files.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(LOCAL_PATH)/common.mk
-
-# Validate all key maps.
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := validate_framework_keymaps
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-intermediates := $(call intermediates-dir-for,ETC,$(LOCAL_MODULE),,COMMON)
-LOCAL_BUILT_MODULE := $(intermediates)/stamp
-
-validatekeymaps := $(HOST_OUT_EXECUTABLES)/validatekeymaps$(HOST_EXECUTABLE_SUFFIX)
-$(LOCAL_BUILT_MODULE): PRIVATE_VALIDATEKEYMAPS := $(validatekeymaps)
-$(LOCAL_BUILT_MODULE) : $(framework_keylayouts) $(framework_keycharmaps) $(framework_keyconfigs) | $(validatekeymaps)
-	$(hide) $(PRIVATE_VALIDATEKEYMAPS) -q $^
-	$(hide) mkdir -p $(dir $@) && touch $@
-
-# Run validatekeymaps uncondionally for platform build.
-droidcore : $(LOCAL_BUILT_MODULE)
-
-# Reset temp vars.
-validatekeymaps :=
-framework_keylayouts :=
-framework_keycharmaps :=
-framework_keyconfigs :=
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 31092536..51b720d 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -430,19 +430,17 @@
 key 659   MACRO_4
 
 # Keys defined by HID usages
-key usage 0x0c0067 WINDOW
-key usage 0x0c006F BRIGHTNESS_UP
-key usage 0x0c0070 BRIGHTNESS_DOWN
-key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP
-key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN
-key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE
-key usage 0x0c0173 MEDIA_AUDIO_TRACK
-key usage 0x0c019C PROFILE_SWITCH
-key usage 0x0c01A2 ALL_APPS
-# TODO(b/297094448): Add stylus button mappings as a fallback when we have a way to determine
-#   if a device can actually report it.
-# key usage 0x0d0044 STYLUS_BUTTON_PRIMARY
-# key usage 0x0d005a STYLUS_BUTTON_SECONDARY
+key usage 0x0c0067 WINDOW                    FALLBACK_USAGE_MAPPING
+key usage 0x0c006F BRIGHTNESS_UP             FALLBACK_USAGE_MAPPING
+key usage 0x0c0070 BRIGHTNESS_DOWN           FALLBACK_USAGE_MAPPING
+key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP     FALLBACK_USAGE_MAPPING
+key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN   FALLBACK_USAGE_MAPPING
+key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING
+key usage 0x0c0173 MEDIA_AUDIO_TRACK         FALLBACK_USAGE_MAPPING
+key usage 0x0c019C PROFILE_SWITCH            FALLBACK_USAGE_MAPPING
+key usage 0x0c01A2 ALL_APPS                  FALLBACK_USAGE_MAPPING
+key usage 0x0d0044 STYLUS_BUTTON_PRIMARY     FALLBACK_USAGE_MAPPING
+key usage 0x0d005a STYLUS_BUTTON_SECONDARY   FALLBACK_USAGE_MAPPING
 
 # Joystick and game controller axes.
 # Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
diff --git a/data/keyboards/common.mk b/data/keyboards/common.mk
deleted file mode 100644
index d75b691..0000000
--- a/data/keyboards/common.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (C) 2010 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 is the list of framework provided keylayouts and key character maps to include.
-# Used by Android.mk and keyboards.mk.
-
-framework_keylayouts := $(wildcard $(LOCAL_PATH)/*.kl)
-
-framework_keycharmaps := $(wildcard $(LOCAL_PATH)/*.kcm)
-
-framework_keyconfigs := $(wildcard $(LOCAL_PATH)/*.idc)
diff --git a/keystore/java/android/security/AndroidProtectedConfirmation.java b/keystore/java/android/security/AndroidProtectedConfirmation.java
index dfe485a..268e0a5 100644
--- a/keystore/java/android/security/AndroidProtectedConfirmation.java
+++ b/keystore/java/android/security/AndroidProtectedConfirmation.java
@@ -59,6 +59,10 @@
 
     /**
      * Requests keystore call into the confirmationui HAL to display a prompt.
+     * @deprecated Android Protected Confirmation had a low adoption rate among Android device
+     *             makers and developers alike. Given the lack of devices supporting the
+     *             feature, it is deprecated. Developers can use auth-bound Keystore keys
+     *             as a partial replacement.
      *
      * @param listener the binder to use for callbacks.
      * @param promptText the prompt to display.
@@ -68,6 +72,7 @@
      * @return one of the {@code CONFIRMATIONUI_*} constants, for
      * example {@code KeyStore.CONFIRMATIONUI_OK}.
      */
+    @Deprecated
     public int presentConfirmationPrompt(IConfirmationCallback listener, String promptText,
                                          byte[] extraData, String locale, int uiOptionsAsFlags) {
         try {
@@ -84,11 +89,16 @@
 
     /**
      * Requests keystore call into the confirmationui HAL to cancel displaying a prompt.
+     * @deprecated Android Protected Confirmation had a low adoption rate among Android device
+     *             makers and developers alike. Given the lack of devices supporting the
+     *             feature, it is deprecated. Developers can use auth-bound Keystore keys
+     *             as a partial replacement.
      *
      * @param listener the binder passed to the {@link #presentConfirmationPrompt} method.
      * @return one of the {@code CONFIRMATIONUI_*} constants, for
      * example {@code KeyStore.CONFIRMATIONUI_OK}.
      */
+    @Deprecated
     public int cancelConfirmationPrompt(IConfirmationCallback listener) {
         try {
             getService().cancelPrompt(listener);
@@ -103,9 +113,14 @@
 
     /**
      * Requests keystore to check if the confirmationui HAL is available.
+     * @deprecated Android Protected Confirmation had a low adoption rate among Android device
+     *             makers and developers alike. Given the lack of devices supporting the
+     *             feature, it is deprecated. Developers can use auth-bound Keystore keys
+     *             as a partial replacement.
      *
      * @return whether the confirmationUI HAL is available.
      */
+    @Deprecated
     public boolean isConfirmationPromptSupported() {
         try {
             return getService().isSupported();
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 4ec5e1b..6404c4b 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -100,12 +100,14 @@
      *
      * @param userId - the user's Android user ID
      * @param unlockingSids - list of biometric SIDs with which the device may be unlocked again
+     * @param weakUnlockEnabled - true if non-strong biometric or trust agent unlock is enabled
      * @return 0 if successful or a {@code ResponseCode}.
      */
-    public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids) {
+    public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
+            boolean weakUnlockEnabled) {
         StrictMode.noteDiskWrite();
         try {
-            getService().onDeviceLocked(userId, unlockingSids);
+            getService().onDeviceLocked(userId, unlockingSids, weakUnlockEnabled);
             return 0;
         } catch (RemoteException | NullPointerException e) {
             Log.w(TAG, "Can not connect to keystore", e);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 9c05a3a..83ddfc5 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -109,13 +109,29 @@
         }
     }
 
+    // For curve 25519, KeyMint uses the KM_ALGORITHM_EC constant, but in the Java layer we need
+    // to distinguish between Curve 25519 and other EC algorithms, so we use a different constant
+    // with a value that is outside the range of the enum used for KeyMint algorithms.
+    private static final int ALGORITHM_XDH = KeymasterDefs.KM_ALGORITHM_EC + 1200;
+    private static final int ALGORITHM_ED25519 = ALGORITHM_XDH + 1;
+
     /**
-     * XDH represents Curve 25519 providers.
+     * XDH represents Curve 25519 agreement key provider.
      */
     public static class XDH extends AndroidKeyStoreKeyPairGeneratorSpi {
         // XDH is treated as EC.
         public XDH() {
-            super(KeymasterDefs.KM_ALGORITHM_EC);
+            super(ALGORITHM_XDH);
+        }
+    }
+
+    /**
+     * ED25519 represents Curve 25519 signing key provider.
+     */
+    public static class ED25519 extends AndroidKeyStoreKeyPairGeneratorSpi {
+        // ED25519 is treated as EC.
+        public ED25519() {
+            super(ALGORITHM_ED25519);
         }
     }
 
@@ -241,7 +257,9 @@
 
             KeyGenParameterSpec spec;
             boolean encryptionAtRestRequired = false;
-            int keymasterAlgorithm = mOriginalKeymasterAlgorithm;
+            int keymasterAlgorithm = (mOriginalKeymasterAlgorithm == ALGORITHM_XDH
+                    || mOriginalKeymasterAlgorithm == ALGORITHM_ED25519)
+                    ? KeymasterDefs.KM_ALGORITHM_EC : mOriginalKeymasterAlgorithm;
             if (params instanceof KeyGenParameterSpec) {
                 spec = (KeyGenParameterSpec) params;
             } else if (params instanceof KeyPairGeneratorSpec) {
@@ -610,6 +628,15 @@
                 if (algSpecificSpec instanceof ECGenParameterSpec) {
                     ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec;
                     mEcCurveName = ecSpec.getName();
+                    if (mOriginalKeymasterAlgorithm == ALGORITHM_XDH
+                            && !mEcCurveName.equalsIgnoreCase("x25519")) {
+                        throw new InvalidAlgorithmParameterException("XDH algorithm only supports"
+                                + " x25519 curve.");
+                    } else if (mOriginalKeymasterAlgorithm == ALGORITHM_ED25519
+                            && !mEcCurveName.equalsIgnoreCase("ed25519")) {
+                        throw new InvalidAlgorithmParameterException("Ed25519 algorithm only"
+                                + " supports ed25519 curve.");
+                    }
                     final Integer ecSpecKeySizeBits = SUPPORTED_EC_CURVE_NAME_TO_SIZE.get(
                             mEcCurveName.toLowerCase(Locale.US));
                     if (ecSpecKeySizeBits == null) {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index 11278e8..d204f13 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -86,11 +86,14 @@
         put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC");
         put("KeyPairGenerator.RSA", PACKAGE_NAME +  ".AndroidKeyStoreKeyPairGeneratorSpi$RSA");
         put("KeyPairGenerator.XDH", PACKAGE_NAME +  ".AndroidKeyStoreKeyPairGeneratorSpi$XDH");
+        put("KeyPairGenerator.ED25519", PACKAGE_NAME
+                +  ".AndroidKeyStoreKeyPairGeneratorSpi$ED25519");
 
         // java.security.KeyFactory
         putKeyFactoryImpl("EC");
         putKeyFactoryImpl("RSA");
         putKeyFactoryImpl("XDH");
+        putKeyFactoryImpl("ED25519");
 
         // javax.crypto.KeyGenerator
         put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 7dec12a..2c0ba92 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -479,18 +479,20 @@
     private void startFadeAnimation(@NonNull SurfaceControl leash, boolean show) {
         final float end = show ? 1.f : 0.f;
         final float start = 1.f - end;
-        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
         final ValueAnimator va = ValueAnimator.ofFloat(start, end);
         va.setDuration(FADE_DURATION);
         va.setInterpolator(show ? ALPHA_IN : ALPHA_OUT);
         va.addUpdateListener(animation -> {
             float fraction = animation.getAnimatedFraction();
+            final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
             transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
             transaction.apply();
+            mTransactionPool.release(transaction);
         });
         va.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
                 transaction.setAlpha(leash, end);
                 transaction.apply();
                 mTransactionPool.release(transaction);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index bb62846..e0ccf17 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -210,15 +210,6 @@
     path: "apex/java",
 }
 
-java_api_contribution {
-    name: "framework-graphics-public-stubs",
-    api_surface: "public",
-    api_file: "api/current.txt",
-    visibility: [
-        "//build/orchestrator/apis",
-    ],
-}
-
 // ------------------------
 // APEX
 // ------------------------
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 1dd22cf..a733d17 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -218,7 +218,7 @@
  * stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer.
  */
 void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
-    if (mDamageGenerationId == info.damageGenerationId) {
+    if (mDamageGenerationId == info.damageGenerationId && mDamageGenerationId != 0) {
         // We hit the same node a second time in the same tree. We don't know the minimal
         // damage rect anymore, so just push the biggest we can onto our parent's transform
         // We push directly onto parent in case we are clipped to bounds but have moved position.
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 572635a..4d03bf1 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -263,7 +263,7 @@
     DisplayList mDisplayList;
     DisplayList mStagingDisplayList;
 
-    int64_t mDamageGenerationId;
+    int64_t mDamageGenerationId = 0;
 
     friend class AnimatorManager;
     AnimatorManager mAnimatorManager;
diff --git a/libs/hwui/jni/PathMeasure.cpp b/libs/hwui/jni/PathMeasure.cpp
index acf893e..79acb6c 100644
--- a/libs/hwui/jni/PathMeasure.cpp
+++ b/libs/hwui/jni/PathMeasure.cpp
@@ -17,7 +17,11 @@
 
 #include "GraphicsJNI.h"
 
+#include "SkMatrix.h"
+#include "SkPath.h"
 #include "SkPathMeasure.h"
+#include "SkPoint.h"
+#include "SkScalar.h"
 
 /*  We declare an explicit pair, so that we don't have to rely on the java
     client to be sure not to edit the path while we have an active measure
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index cf31173..56f1e64 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -115,7 +115,7 @@
         const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
         const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
         const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
-        const HardwareBufferRenderParams& bufferParams) {
+        const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
     if (!isCapturingSkp() && !mHardwareBuffer) {
         mEglManager.damageFrame(frame, dirty);
     }
@@ -167,6 +167,7 @@
     // Draw visual debugging features
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
                     ProfileType::None != Properties::getProfileType())) {
+        std::scoped_lock lock(profilerLock);
         SkCanvas* profileCanvas = surface->getCanvas();
         SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
         profiler->draw(profileRenderer);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index f0461be..0325593 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -42,7 +42,8 @@
             const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
             const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
             const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
-            const renderthread::HardwareBufferRenderParams& bufferParams) override;
+            const renderthread::HardwareBufferRenderParams& bufferParams,
+            std::mutex& profilerLock) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
     bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
                      FrameInfo* currentFrameInfo, bool* requireSwap) override;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 86096d5..7d19232 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -75,7 +75,7 @@
         const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
         const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
         const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
-        const HardwareBufferRenderParams& bufferParams) {
+        const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
     sk_sp<SkSurface> backBuffer;
     SkMatrix preTransform;
     if (mHardwareBuffer) {
@@ -103,6 +103,7 @@
     // Draw visual debugging features
     if (CC_UNLIKELY(Properties::showDirtyRegions ||
                     ProfileType::None != Properties::getProfileType())) {
+        std::scoped_lock lock(profilerLock);
         SkCanvas* profileCanvas = backBuffer->getCanvas();
         SkAutoCanvasRestore saver(profileCanvas, true);
         profileCanvas->concat(mVkSurface->getCurrentPreTransform());
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 284cde5..37b86f1 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -42,7 +42,8 @@
             const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
             const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
             const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
-            const renderthread::HardwareBufferRenderParams& bufferParams) override;
+            const renderthread::HardwareBufferRenderParams& bufferParams,
+            std::mutex& profilerLock) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
     bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
                      FrameInfo* currentFrameInfo, bool* requireSwap) override;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 618c896..f690783 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -592,14 +592,10 @@
     {
         // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
         // or it can lead to memory corruption.
-        // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
-        // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
-        // the thread we're primarily concerned about being responsive, this being too broad
-        // shouldn't pose a performance issue.
-        std::scoped_lock lock(mFrameMetricsReporterMutex);
         drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
                                            &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
-                                           mLightInfo, mRenderNodes, &(profiler()), mBufferParams);
+                                           mLightInfo, mRenderNodes, &(profiler()), mBufferParams,
+                                           profilerLock());
     }
 
     uint64_t frameCompleteNr = getFrameNumber();
@@ -729,7 +725,7 @@
             mCurrentFrameInfo->markFrameCompleted();
             mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
                     = mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
-            std::scoped_lock lock(mFrameMetricsReporterMutex);
+            std::scoped_lock lock(mFrameInfoMutex);
             mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter, frameCompleteNr,
                                      mSurfaceControlGenerationId);
         }
@@ -758,7 +754,7 @@
 
 void CanvasContext::reportMetricsWithPresentTime() {
     {  // acquire lock
-        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        std::scoped_lock lock(mFrameInfoMutex);
         if (mFrameMetricsReporter == nullptr) {
             return;
         }
@@ -793,7 +789,7 @@
 
     forthBehind->set(FrameInfoIndex::DisplayPresentTime) = presentTime;
     {  // acquire lock
-        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        std::scoped_lock lock(mFrameInfoMutex);
         if (mFrameMetricsReporter != nullptr) {
             mFrameMetricsReporter->reportFrameMetrics(forthBehind->data(), true /*hasPresentTime*/,
                                                       frameNumber, surfaceControlId);
@@ -802,7 +798,7 @@
 }
 
 void CanvasContext::addFrameMetricsObserver(FrameMetricsObserver* observer) {
-    std::scoped_lock lock(mFrameMetricsReporterMutex);
+    std::scoped_lock lock(mFrameInfoMutex);
     if (mFrameMetricsReporter.get() == nullptr) {
         mFrameMetricsReporter.reset(new FrameMetricsReporter());
     }
@@ -816,7 +812,7 @@
 }
 
 void CanvasContext::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
-    std::scoped_lock lock(mFrameMetricsReporterMutex);
+    std::scoped_lock lock(mFrameInfoMutex);
     if (mFrameMetricsReporter.get() != nullptr) {
         mFrameMetricsReporter->removeObserver(observer);
         if (!mFrameMetricsReporter->hasObservers()) {
@@ -853,7 +849,7 @@
     FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
 
     if (frameInfo != nullptr) {
-        std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
+        std::scoped_lock lock(instance->mFrameInfoMutex);
         frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
                 frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
         frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max(
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 32ac5af..3978fbc 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -161,6 +161,7 @@
     void notifyFramePending();
 
     FrameInfoVisualizer& profiler() { return mProfiler; }
+    std::mutex& profilerLock() { return mFrameInfoMutex; }
 
     void dumpFrames(int fd);
     void resetFrameStats();
@@ -340,8 +341,8 @@
     JankTracker mJankTracker;
     FrameInfoVisualizer mProfiler;
     std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter
-            GUARDED_BY(mFrameMetricsReporterMutex);
-    std::mutex mFrameMetricsReporterMutex;
+            GUARDED_BY(mFrameInfoMutex);
+    std::mutex mFrameInfoMutex;
 
     std::set<RenderNode*> mPrefetchedLayers;
 
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 6c2cb9d..023c29a 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -67,7 +67,8 @@
                             const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
                             const std::vector<sp<RenderNode>>& renderNodes,
                             FrameInfoVisualizer* profiler,
-                            const HardwareBufferRenderParams& bufferParams) = 0;
+                            const HardwareBufferRenderParams& bufferParams,
+                            std::mutex& profilerLock) = 0;
     virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
                              FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
     virtual DeferredLayerUpdater* createTextureLayer() = 0;
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index b7c97208..862ae8d 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -43,21 +44,25 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.ReadOnlyBufferException;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
+import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
 /**
  MediaCodec class can be used to access low-level media codecs, i.e. encoder/decoder components.
  It is part of the Android low-level multimedia support infrastructure (normally used together
@@ -1824,6 +1829,7 @@
     private static final String EOS_AND_DECODE_ONLY_ERROR_MESSAGE = "An input buffer cannot have "
             + "both BUFFER_FLAG_END_OF_STREAM and BUFFER_FLAG_DECODE_ONLY flags";
     private static final int CB_CRYPTO_ERROR = 6;
+    private static final int CB_LARGE_FRAME_OUTPUT_AVAILABLE = 7;
 
     private class EventHandler extends Handler {
         private MediaCodec mCodec;
@@ -1945,6 +1951,39 @@
                     break;
                 }
 
+                case CB_LARGE_FRAME_OUTPUT_AVAILABLE:
+                {
+                    int index = msg.arg2;
+                    ArrayDeque<BufferInfo> infos = (ArrayDeque<BufferInfo>)msg.obj;
+                    synchronized(mBufferLock) {
+                        switch (mBufferMode) {
+                            case BUFFER_MODE_LEGACY:
+                                validateOutputByteBuffersLocked(mCachedOutputBuffers,
+                                        index, infos);
+                                break;
+                            case BUFFER_MODE_BLOCK:
+                                while (mOutputFrames.size() <= index) {
+                                    mOutputFrames.add(null);
+                                }
+                                OutputFrame frame = mOutputFrames.get(index);
+                                if (frame == null) {
+                                    frame = new OutputFrame(index);
+                                    mOutputFrames.set(index, frame);
+                                }
+                                frame.setBufferInfos(infos);
+                                frame.setAccessible(true);
+                                break;
+                            default:
+                                throw new IllegalArgumentException(
+                                        "Unrecognized buffer mode: for large frame output");
+                        }
+                    }
+                    mCallback.onOutputBuffersAvailable(
+                            mCodec, index, infos);
+
+                    break;
+                }
+
                 case CB_ERROR:
                 {
                     mCallback.onError(mCodec, (MediaCodec.CodecException) msg.obj);
@@ -2836,11 +2875,72 @@
         }
     }
 
+    /**
+     * Submit multiple access units to the codec along with multiple
+     * {@link MediaCodec.BufferInfo} describing the contents of the buffer. This method
+     * is supported only in asynchronous mode. While this method can be used for all codecs,
+     * it is meant for buffer batching, which is only supported by codecs that advertise
+     * FEATURE_MultipleFrames. Other codecs will not output large output buffers via
+     * onOutputBuffersAvailable, and instead will output single-access-unit output via
+     * onOutputBufferAvailable.
+     * <p>
+     * Output buffer size can be configured using the following MediaFormat keys.
+     * {@link MediaFormat#KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE} and
+     * {@link MediaFormat#KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE}.
+     * Details for each access unit present in the buffer should be described using
+     * {@link MediaCodec.BufferInfo}. Access units must be laid out contiguously (without any gaps)
+     * and in order. Multiple access units in the output if present, will be available in
+     * {@link Callback#onOutputBuffersAvailable} or {@link Callback#onOutputBufferAvailable}
+     * in case of single-access-unit output or when output does not contain any buffers,
+     * such as flags.
+     * <p>
+     * All other details for populating {@link MediaCodec.BufferInfo} is the same as described in
+     * {@link #queueInputBuffer}.
+     *
+     * @param index The index of a client-owned input buffer previously returned
+     *              in a call to {@link #dequeueInputBuffer}.
+     * @param bufferInfos ArrayDeque of {@link MediaCodec.BufferInfo} that describes the
+     *                    contents in the buffer. The ArrayDeque and the BufferInfo objects provided
+     *                    can be recycled by the caller for re-use.
+     * @throws IllegalStateException if not in the Executing state or not in asynchronous mode.
+     * @throws MediaCodec.CodecException upon codec error.
+     * @throws IllegalArgumentException upon if bufferInfos is empty, contains null, or if the
+     *                    access units are not contiguous.
+     * @throws CryptoException if a crypto object has been specified in
+     *         {@link #configure}
+     */
+    @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+    public final void queueInputBuffers(
+            int index,
+            @NonNull ArrayDeque<BufferInfo> bufferInfos) {
+        synchronized(mBufferLock) {
+            if (mBufferMode == BUFFER_MODE_BLOCK) {
+                throw new IncompatibleWithBlockModelException("queueInputBuffers() "
+                        + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. "
+                        + "Please use getQueueRequest() to queue buffers");
+            }
+            invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */);
+            mDequeuedInputBuffers.remove(index);
+        }
+        try {
+            native_queueInputBuffers(
+                    index, bufferInfos.toArray());
+        } catch (CryptoException | IllegalStateException | IllegalArgumentException e) {
+            revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
+            throw e;
+        }
+    }
+
     private native final void native_queueInputBuffer(
             int index,
             int offset, int size, long presentationTimeUs, int flags)
         throws CryptoException;
 
+    private native final void native_queueInputBuffers(
+            int index,
+            @NonNull Object[] infos)
+        throws CryptoException, CodecException;
+
     public static final int CRYPTO_MODE_UNENCRYPTED = 0;
     public static final int CRYPTO_MODE_AES_CTR     = 1;
     public static final int CRYPTO_MODE_AES_CBC     = 2;
@@ -3462,6 +3562,26 @@
         }
 
         /**
+         * Sets MediaCodec.BufferInfo objects describing the access units
+         * contained in this queue request. Access units must be laid out
+         * contiguously without gaps and in order.
+         *
+         * @param infos Represents {@link MediaCodec.BufferInfo} objects to mark
+         *              individual access-unit boundaries and the timestamps associated with it.
+         *              The buffer is expected to contain the data in a continuous manner.
+         * @return this object
+         */
+        @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+        public @NonNull QueueRequest setBufferInfos(@NonNull ArrayDeque<BufferInfo> infos) {
+            if (!isAccessible()) {
+                throw new IllegalStateException("The request is stale");
+            }
+            mBufferInfos.clear();
+            mBufferInfos.addAll(infos);
+            return this;
+        }
+
+        /**
          * Add an integer parameter.
          * See {@link MediaFormat} for an exhaustive list of supported keys with
          * values of type int, that can also be set with {@link MediaFormat#setInteger}.
@@ -3577,10 +3697,18 @@
                 throw new IllegalStateException("No block is set");
             }
             setAccessible(false);
+            if (mBufferInfos.isEmpty()) {
+                BufferInfo info = new BufferInfo();
+                info.size = mSize;
+                info.offset = mOffset;
+                info.presentationTimeUs = mPresentationTimeUs;
+                info.flags = mFlags;
+                mBufferInfos.add(info);
+            }
             if (mLinearBlock != null) {
                 mCodec.native_queueLinearBlock(
-                        mIndex, mLinearBlock, mOffset, mSize, mCryptoInfo,
-                        mPresentationTimeUs, mFlags,
+                        mIndex, mLinearBlock, mCryptoInfo,
+                        mBufferInfos.toArray(),
                         mTuningKeys, mTuningValues);
             } else if (mHardwareBuffer != null) {
                 mCodec.native_queueHardwareBuffer(
@@ -3598,6 +3726,7 @@
             mHardwareBuffer = null;
             mPresentationTimeUs = 0;
             mFlags = 0;
+            mBufferInfos.clear();
             mTuningKeys.clear();
             mTuningValues.clear();
             return this;
@@ -3621,6 +3750,7 @@
         private HardwareBuffer mHardwareBuffer = null;
         private long mPresentationTimeUs = 0;
         private @BufferFlag int mFlags = 0;
+        private final ArrayDeque<BufferInfo> mBufferInfos = new ArrayDeque<>();
         private final ArrayList<String> mTuningKeys = new ArrayList<>();
         private final ArrayList<Object> mTuningValues = new ArrayList<>();
 
@@ -3630,11 +3760,8 @@
     private native void native_queueLinearBlock(
             int index,
             @NonNull LinearBlock block,
-            int offset,
-            int size,
             @Nullable CryptoInfo cryptoInfo,
-            long presentationTimeUs,
-            int flags,
+            @NonNull Object[] bufferInfos,
             @NonNull ArrayList<String> keys,
             @NonNull ArrayList<Object> values);
 
@@ -4048,6 +4175,27 @@
         }
     }
 
+    private void validateOutputByteBuffersLocked(
+        @Nullable ByteBuffer[] buffers, int index, @NonNull ArrayDeque<BufferInfo> infoDeque) {
+        Optional<BufferInfo> minInfo = infoDeque.stream().min(
+                (info1, info2) -> Integer.compare(info1.offset, info2.offset));
+        Optional<BufferInfo> maxInfo = infoDeque.stream().max(
+                (info1, info2) -> Integer.compare(info1.offset, info2.offset));
+        if (buffers == null) {
+            if (index >= 0) {
+                mValidOutputIndices.set(index);
+            }
+        } else if (index >= 0 && index < buffers.length) {
+            ByteBuffer buffer = buffers[index];
+            if (buffer != null && minInfo.isPresent() && maxInfo.isPresent()) {
+                buffer.setAccessible(true);
+                buffer.limit(maxInfo.get().offset + maxInfo.get().size);
+                buffer.position(minInfo.get().offset);
+            }
+        }
+
+    }
+
     private void validateOutputByteBufferLocked(
             @Nullable ByteBuffer[] buffers, int index, @NonNull BufferInfo info) {
         if (buffers == null) {
@@ -4405,6 +4553,22 @@
             return mFlags;
         }
 
+        /*
+         * Returns the BufferInfos associated with this OutputFrame. These BufferInfos
+         * describes the access units present in the OutputFrame. Access units are laid
+         * out contiguously without gaps and in order.
+         */
+        @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+        public @NonNull ArrayDeque<BufferInfo> getBufferInfos() {
+            if (mBufferInfos.isEmpty()) {
+                // single BufferInfo could be present.
+                BufferInfo bufferInfo = new BufferInfo();
+                bufferInfo.set(0, 0, mPresentationTimeUs, mFlags);
+                mBufferInfos.add(bufferInfo);
+            }
+            return mBufferInfos;
+        }
+
         /**
          * Returns a read-only {@link MediaFormat} for this frame. The returned
          * object is valid only until the client calls {@link MediaCodec#releaseOutputBuffer}.
@@ -4430,6 +4594,7 @@
             mLinearBlock = null;
             mHardwareBuffer = null;
             mFormat = null;
+            mBufferInfos.clear();
             mChangedKeys.clear();
             mKeySet.clear();
             mLoaded = false;
@@ -4448,6 +4613,11 @@
             mFlags = info.flags;
         }
 
+        void setBufferInfos(ArrayDeque<BufferInfo> infos) {
+            mBufferInfos.clear();
+            mBufferInfos.addAll(infos);
+        }
+
         boolean isLoaded() {
             return mLoaded;
         }
@@ -4462,6 +4632,7 @@
         private long mPresentationTimeUs = 0;
         private @BufferFlag int mFlags = 0;
         private MediaFormat mFormat = null;
+        private final ArrayDeque<BufferInfo> mBufferInfos = new ArrayDeque<>();
         private final ArrayList<String> mChangedKeys = new ArrayList<>();
         private final Set<String> mKeySet = new HashSet<>();
         private boolean mAccessible = false;
@@ -5170,6 +5341,32 @@
                 @NonNull MediaCodec codec, int index, @NonNull BufferInfo info);
 
         /**
+         * Called when multiple access-units are available in the output.
+         *
+         * @param codec The MediaCodec object.
+         * @param index The index of the available output buffer.
+         * @param infos Infos describing the available output buffer {@link MediaCodec.BufferInfo}.
+         *              Access units present in the output buffer are laid out contiguously
+         *              without gaps and in order.
+         */
+        @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+        public void onOutputBuffersAvailable(
+                @NonNull MediaCodec codec, int index, @NonNull ArrayDeque<BufferInfo> infos) {
+            /*
+             * This callback returns multiple BufferInfos when codecs are configured to operate on
+             * large audio frame. Since at this point, we have a single large buffer, returning
+             * each BufferInfo using
+             * {@link Callback#onOutputBufferAvailable onOutputBufferAvailable} may cause the
+             * index to be released to the codec using {@link MediaCodec#releaseOutputBuffer}
+             * before all BuffersInfos can be returned to the client.
+             * Hence this callback is required to be implemented or else an exception is thrown.
+             */
+            throw new IllegalStateException(
+                    "Client must override onOutputBuffersAvailable when codec is " +
+                    "configured to operate with multiple access units");
+        }
+
+        /**
          * Called when the MediaCodec encountered an error
          *
          * @param codec The MediaCodec object.
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 46db777..5e40eee 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -16,6 +16,10 @@
 
 package android.media;
 
+import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE;
+import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -118,6 +122,10 @@
  * <tr><td>{@link #KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT}</td>
  *     <td>Integer</td><td><b>decoder-only</b>, optional, if content is MPEG-H audio,
  *         specifies the preferred reference channel layout of the stream.</td></tr>
+ * <tr><td>{@link #KEY_MAX_BUFFER_BATCH_OUTPUT_SIZE}</td><td>Integer</td><td>optional, used with
+ *         large audio frame support, specifies max size of output buffer in bytes.</td></tr>
+ * <tr><td>{@link #KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE}</td><td>Integer</td><td>optional,
+ *         used with large audio frame support, specifies threshold output size in bytes.</td></tr>
  * </table>
  *
  * Subtitle formats have the following keys:
@@ -456,6 +464,50 @@
     public static final String KEY_MAX_INPUT_SIZE = "max-input-size";
 
     /**
+     * A key describing the maximum output buffer size in bytes when using
+     * large buffer mode containing multiple access units.
+     *
+     * When not-set - codec functions with one access-unit per frame.
+     * When set less than the size of two access-units - will make codec
+     * operate in single access-unit per output frame.
+     * When set to a value too big - The component or the framework will
+     * override this value to a reasonable max size not exceeding typical
+     * 10 seconds of data (device dependent) when set to a value larger than
+     * that. The value final value used will be returned in the output format.
+     *
+     * The associated value is an integer
+     *
+     * @see FEATURE_MultipleFrames
+     */
+    @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+    public static final String KEY_BUFFER_BATCH_MAX_OUTPUT_SIZE = "buffer-batch-max-output-size";
+
+    /**
+     * A key describing the threshold output size in bytes when using large buffer
+     * mode containing multiple access units.
+     *
+     * This is an optional parameter.
+     *
+     * If not set - the component can set this to a reasonable value.
+     * If set larger than max size, the components will
+     * clip this setting to maximum buffer batching output size.
+     *
+     * The component will return a partial output buffer if the output buffer reaches or
+     * surpass this limit.
+     *
+     * Threshold size should be always less or equal to KEY_MAX_BUFFER_BATCH_OUTPUT_SIZE.
+     * The final setting of this value as determined by the component will be returned
+     * in the output format
+     *
+     * The associated value is an integer
+     *
+     * @see FEATURE_MultipleFrames
+     */
+    @FlaggedApi(FLAG_LARGE_AUDIO_FRAME)
+    public static final String KEY_BUFFER_BATCH_THRESHOLD_OUTPUT_SIZE =
+            "buffer-batch-threshold-output-size";
+
+    /**
      * A key describing the pixel aspect ratio width.
      * The associated value is an integer
      */
@@ -1635,6 +1687,34 @@
      */
     public static final String KEY_ALLOW_FRAME_DROP = "allow-frame-drop";
 
+    /**
+     * A key describing the desired codec importance for the application.
+     * <p>
+     * The associated value is a positive integer including zero.
+     * Higher value means lesser importance.
+     * <p>
+     * The resource manager may use the codec importance, along with other factors
+     * when reclaiming codecs from an application.
+     * The specifics of reclaim policy is device dependent, but specifying the codec importance,
+     * will allow the resource manager to prioritize reclaiming less important codecs
+     * (assigned higher values) from the (reclaim) requesting application first.
+     * So, the codec importance is only relevant within the context of that application.
+     * <p>
+     * The codec importance can be set:
+     * <ul>
+     * <li>through {@link MediaCodec#configure}. </li>
+     * <li>through {@link MediaCodec#setParameters} if the codec has been configured already,
+     * which allows the users to change the codec importance multiple times.
+     * </ul>
+     * Any change/update in codec importance is guaranteed upon the completion of the function call
+     * that sets the codec importance. So, in case of concurrent codec operations,
+     * make sure to wait for the change in codec importance, before using another codec.
+     * Note that unless specified, by default the codecs will have highest importance (of value 0).
+     *
+     */
+    @FlaggedApi(FLAG_CODEC_IMPORTANCE)
+    public static final String KEY_IMPORTANCE = "importance";
+
     /* package private */ MediaFormat(@NonNull Map<String, Object> map) {
         mMap = map;
     }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1ee5aa3..0d9acf2 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1156,6 +1156,7 @@
             setDataSource(afd);
             return true;
         } catch (NullPointerException | SecurityException | IOException ex) {
+            Log.w(TAG, "Error setting data source via ContentResolver", ex);
             return false;
         }
     }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index ef90bf9..8cdd59e 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -163,6 +163,13 @@
 static struct {
     jclass clazz;
     jmethodID ctorId;
+    jmethodID sizeId;
+    jmethodID addId;
+} gArrayDequeInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctorId;
     jmethodID setInternalStateId;
     jfieldID contextId;
     jfieldID validId;
@@ -200,8 +207,14 @@
     jfieldID queueRequestIndexID;
     jfieldID outputFrameLinearBlockID;
     jfieldID outputFrameHardwareBufferID;
+    jfieldID outputFramebufferInfosID;
     jfieldID outputFrameChangedKeysID;
     jfieldID outputFrameFormatID;
+    jfieldID bufferInfoFlags;
+    jfieldID bufferInfoOffset;
+    jfieldID bufferInfoSize;
+    jfieldID bufferInfoPresentationTimeUs;
+
 };
 
 static fields_t gFields;
@@ -412,6 +425,22 @@
             index, offset, size, timeUs, flags, errorDetailMsg);
 }
 
+status_t JMediaCodec::queueInputBuffers(
+        size_t index,
+        size_t offset,
+        size_t size,
+        const sp<RefBase> &infos,
+        AString *errorDetailMsg) {
+
+    sp<BufferInfosWrapper> auInfo((BufferInfosWrapper *)infos.get());
+    return mCodec->queueInputBuffers(
+            index,
+            offset,
+            size,
+            auInfo,
+            errorDetailMsg);
+}
+
 status_t JMediaCodec::queueSecureInputBuffer(
         size_t index,
         size_t offset,
@@ -430,10 +459,11 @@
 }
 
 status_t JMediaCodec::queueBuffer(
-        size_t index, const std::shared_ptr<C2Buffer> &buffer, int64_t timeUs,
-        uint32_t flags, const sp<AMessage> &tunings, AString *errorDetailMsg) {
+        size_t index, const std::shared_ptr<C2Buffer> &buffer,
+        const sp<RefBase> &infos, const sp<AMessage> &tunings, AString *errorDetailMsg) {
+    sp<BufferInfosWrapper> auInfo((BufferInfosWrapper *)infos.get());
     return mCodec->queueBuffer(
-            index, buffer, timeUs, flags, tunings, errorDetailMsg);
+            index, buffer, auInfo, tunings, errorDetailMsg);
 }
 
 status_t JMediaCodec::queueEncryptedLinearBlock(
@@ -446,13 +476,13 @@
         const uint8_t iv[16],
         CryptoPlugin::Mode mode,
         const CryptoPlugin::Pattern &pattern,
-        int64_t presentationTimeUs,
-        uint32_t flags,
+        const sp<RefBase> &infos,
         const sp<AMessage> &tunings,
         AString *errorDetailMsg) {
+    sp<BufferInfosWrapper> auInfo((BufferInfosWrapper *)infos.get());
     return mCodec->queueEncryptedBuffer(
             index, buffer, offset, subSamples, numSubSamples, key, iv, mode, pattern,
-            presentationTimeUs, flags, tunings, errorDetailMsg);
+            auInfo, tunings, errorDetailMsg);
 }
 
 status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
@@ -722,6 +752,42 @@
     return OK;
 }
 
+void maybeSetBufferInfos(JNIEnv *env, jobject &frame, const sp<BufferInfosWrapper> &bufInfos) {
+    if (!bufInfos) {
+        return;
+    }
+    std::vector<AccessUnitInfo> &infos = bufInfos.get()->value;
+    if (infos.empty()) {
+        return;
+    }
+    ScopedLocalRef<jobject> dequeObj{env, env->NewObject(
+            gArrayDequeInfo.clazz, gArrayDequeInfo.ctorId)};
+    jint offset = 0;
+    std::vector<jobject> jObjectInfos;
+    for (int i = 0 ; i < infos.size(); i++) {
+        jobject bufferInfo = env->NewObject(
+                gBufferInfo.clazz, gBufferInfo.ctorId);
+        if (bufferInfo != NULL) {
+            env->CallVoidMethod(bufferInfo, gBufferInfo.setId,
+                    offset,
+                    (jint)(infos)[i].mSize,
+                    (infos)[i].mTimestamp,
+                    (infos)[i].mFlags);
+            (void)env->CallBooleanMethod(
+                    dequeObj.get(), gArrayDequeInfo.addId, bufferInfo);
+            offset += (infos)[i].mSize;
+            jObjectInfos.push_back(bufferInfo);
+        }
+    }
+    env->SetObjectField(
+            frame,
+            gFields.outputFramebufferInfosID,
+            dequeObj.get());
+    for (int i = 0; i < jObjectInfos.size(); i++) {
+        env->DeleteLocalRef(jObjectInfos[i]);
+    }
+}
+
 status_t JMediaCodec::getOutputFrame(
         JNIEnv *env, jobject frame, size_t index) const {
     sp<MediaCodecBuffer> buffer;
@@ -732,6 +798,11 @@
     }
 
     if (buffer->size() > 0) {
+        sp<RefBase> obj;
+        sp<BufferInfosWrapper> bufInfos;
+        if (buffer->meta()->findObject("accessUnitInfo", &obj)) {
+            bufInfos = std::move(((decltype(bufInfos.get()))obj.get()));
+        }
         std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer();
         if (c2Buffer) {
             switch (c2Buffer->data().type()) {
@@ -747,6 +818,7 @@
                             (jlong)context.release(),
                             true);
                     env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get());
+                    maybeSetBufferInfos(env, frame, bufInfos);
                     break;
                 }
                 case C2BufferData::GRAPHIC: {
@@ -787,6 +859,7 @@
                         (jlong)context.release(),
                         true);
                 env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get());
+                maybeSetBufferInfos(env, frame, bufInfos);
             } else {
                 // No-op.
             }
@@ -1250,6 +1323,7 @@
 void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
     int32_t arg1, arg2 = 0;
     jobject obj = NULL;
+    std::vector<jobject> jObjectInfos;
     CHECK(msg->findInt32("callbackID", &arg1));
     JNIEnv *env = AndroidRuntime::getJNIEnv();
 
@@ -1287,6 +1361,35 @@
             break;
         }
 
+        case MediaCodec::CB_LARGE_FRAME_OUTPUT_AVAILABLE:
+        {
+            sp<RefBase> spobj = nullptr;
+            CHECK(msg->findInt32("index", &arg2));
+            CHECK(msg->findObject("accessUnitInfo", &spobj));
+            if (spobj != nullptr) {
+                sp<BufferInfosWrapper> bufferInfoParamsWrapper {
+                        (BufferInfosWrapper *)spobj.get()};
+                std::vector<AccessUnitInfo> &bufferInfoParams =
+                        bufferInfoParamsWrapper.get()->value;
+                obj = env->NewObject(gArrayDequeInfo.clazz, gArrayDequeInfo.ctorId);
+                jint offset = 0;
+                for (int i = 0 ; i < bufferInfoParams.size(); i++) {
+                    jobject bufferInfo = env->NewObject(gBufferInfo.clazz, gBufferInfo.ctorId);
+                    if (bufferInfo != NULL) {
+                        env->CallVoidMethod(bufferInfo, gBufferInfo.setId,
+                                            offset,
+                                            (jint)(bufferInfoParams)[i].mSize,
+                                            (bufferInfoParams)[i].mTimestamp,
+                                            (bufferInfoParams)[i].mFlags);
+                        (void)env->CallBooleanMethod(obj, gArrayDequeInfo.addId, bufferInfo);
+                        offset += (bufferInfoParams)[i].mSize;
+                        jObjectInfos.push_back(bufferInfo);
+                    }
+                }
+            }
+            break;
+        }
+
         case MediaCodec::CB_CRYPTO_ERROR:
         {
             int32_t err, actionCode;
@@ -1346,6 +1449,9 @@
             arg2,
             obj);
 
+    for (int i = 0; i < jObjectInfos.size(); i++) {
+        env->DeleteLocalRef(jObjectInfos[i]);
+    }
     env->DeleteLocalRef(obj);
 }
 
@@ -1913,6 +2019,103 @@
             codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
 }
 
+static status_t extractInfosFromObject(
+        JNIEnv * const env,
+        jint * const initialOffset,
+        jint * const totalSize,
+        std::vector<AccessUnitInfo> * const infos,
+        const jobjectArray &objArray,
+        AString * const errorDetailMsg) {
+    if (totalSize == nullptr
+            || initialOffset == nullptr
+            || infos == nullptr) {
+        if (errorDetailMsg) {
+            *errorDetailMsg = "Error: Null arguments provided for extracting Access unit info";
+        }
+        return BAD_VALUE;
+    }
+    const jsize numEntries = env->GetArrayLength(objArray);
+    if (numEntries <= 0) {
+        if (errorDetailMsg) {
+            *errorDetailMsg = "Error: No BufferInfo found while queuing for large frame input";
+        }
+        return BAD_VALUE;
+    }
+    *initialOffset = 0;
+    *totalSize = 0;
+    for (jsize i = 0; i < numEntries; i++) {
+        jobject param = env->GetObjectArrayElement(objArray, i);
+        if (param == NULL) {
+            if (errorDetailMsg) {
+                *errorDetailMsg = "Error: Queuing a null BufferInfo";
+            }
+            return BAD_VALUE;
+        }
+        size_t offset = static_cast<size_t>(env->GetIntField(param, gFields.bufferInfoOffset));
+        size_t size = static_cast<size_t>(env->GetIntField(param, gFields.bufferInfoSize));
+        uint32_t flags = static_cast<uint32_t>(env->GetIntField(param, gFields.bufferInfoFlags));
+        if (flags == 0 && size == 0) {
+            if (errorDetailMsg) {
+                *errorDetailMsg = "Error: Queuing an empty BufferInfo";
+            }
+            return BAD_VALUE;
+        }
+        if (i == 0) {
+            *initialOffset = offset;
+        }
+        if (CC_UNLIKELY((offset >  UINT32_MAX)
+                || ((long)(offset + size) > UINT32_MAX)
+                || ((offset - *initialOffset) != *totalSize))) {
+            if (errorDetailMsg) {
+                *errorDetailMsg = "Error: offset/size in BufferInfo";
+            }
+            return BAD_VALUE;
+        }
+        infos->emplace_back(
+                flags,
+                size,
+                env->GetLongField(param, gFields.bufferInfoPresentationTimeUs));
+        *totalSize += size;
+    }
+    return OK;
+}
+
+static void android_media_MediaCodec_queueInputBuffers(
+        JNIEnv *env,
+        jobject thiz,
+        jint index,
+        jobjectArray objArray) {
+    ALOGV("android_media_MediaCodec_queueInputBuffers");
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+    if (codec == NULL || codec->initCheck() != OK || objArray == NULL) {
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
+        return;
+    }
+    sp<BufferInfosWrapper> infoObj =
+            new BufferInfosWrapper{decltype(infoObj->value)()};
+    AString errorDetailMsg;
+    jint initialOffset = 0;
+    jint totalSize = 0;
+    status_t err = extractInfosFromObject(
+            env,
+            &initialOffset,
+            &totalSize,
+            &infoObj->value,
+            objArray,
+            &errorDetailMsg);
+    if (err == OK) {
+        err = codec->queueInputBuffers(
+            index,
+            initialOffset,
+            totalSize,
+            infoObj,
+            &errorDetailMsg);
+    }
+    throwExceptionAsNecessary(
+            env, err, ACTION_CODE_FATAL,
+            codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
+}
+
 struct NativeCryptoInfo {
     NativeCryptoInfo(JNIEnv *env, jobject cryptoInfoObj)
         : mEnv{env},
@@ -2559,8 +2762,7 @@
 
 static void android_media_MediaCodec_native_queueLinearBlock(
         JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
-        jint offset, jint size, jobject cryptoInfoObj,
-        jlong presentationTimeUs, jint flags, jobject keys, jobject values) {
+        jobject cryptoInfoObj, jobjectArray objArray, jobject keys, jobject values) {
     ALOGV("android_media_MediaCodec_native_queueLinearBlock");
 
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -2578,7 +2780,24 @@
                 "error occurred while converting tunings from Java to native");
         return;
     }
-
+    jint totalSize;
+    jint initialOffset;
+    std::vector<AccessUnitInfo> infoVec;
+    AString errorDetailMsg;
+    err = extractInfosFromObject(env,
+            &initialOffset,
+            &totalSize,
+            &infoVec,
+            objArray,
+            &errorDetailMsg);
+    if (err != OK) {
+        throwExceptionAsNecessary(
+                env, INVALID_OPERATION, ACTION_CODE_FATAL,
+                codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
+        return;
+    }
+    sp<BufferInfosWrapper> infos =
+            new BufferInfosWrapper{std::move(infoVec)};
     std::shared_ptr<C2Buffer> buffer;
     sp<hardware::HidlMemory> memory;
     ScopedLocalRef<jobject> lock{env, env->GetObjectField(bufferObj, gLinearBlockInfo.lockId)};
@@ -2587,10 +2806,10 @@
             JMediaCodecLinearBlock *context =
                 (JMediaCodecLinearBlock *)env->GetLongField(bufferObj, gLinearBlockInfo.contextId);
             if (codec->hasCryptoOrDescrambler()) {
-                extractMemoryFromContext(context, offset, size, &memory);
-                offset += context->mHidlMemoryOffset;
+                extractMemoryFromContext(context, initialOffset, totalSize, &memory);
+                initialOffset += context->mHidlMemoryOffset;
             } else {
-                extractBufferFromContext(context, offset, size, &buffer);
+                extractBufferFromContext(context, initialOffset, totalSize, &buffer);
             }
         }
         env->MonitorExit(lock.get());
@@ -2601,7 +2820,6 @@
         return;
     }
 
-    AString errorDetailMsg;
     if (codec->hasCryptoOrDescrambler()) {
         if (!memory) {
             // It means there was an unexpected failure in extractMemoryFromContext above
@@ -2615,7 +2833,7 @@
             return;
         }
         auto cryptoInfo =
-                cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{size};
+                cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{totalSize};
         if (env->ExceptionCheck()) {
             // Creation of cryptoInfo failed. Let the exception bubble up.
             return;
@@ -2623,13 +2841,12 @@
         err = codec->queueEncryptedLinearBlock(
                 index,
                 memory,
-                offset,
+                initialOffset,
                 cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples,
                 (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv,
                 cryptoInfo.mMode,
                 cryptoInfo.mPattern,
-                presentationTimeUs,
-                flags,
+                infos,
                 tunings,
                 &errorDetailMsg);
         ALOGI_IF(err != OK, "queueEncryptedLinearBlock returned err = %d", err);
@@ -2646,7 +2863,7 @@
             return;
         }
         err = codec->queueBuffer(
-                index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
+                index, buffer, infos, tunings, &errorDetailMsg);
     }
     throwExceptionAsNecessary(
             env, err, ACTION_CODE_FATAL,
@@ -2704,8 +2921,11 @@
     std::shared_ptr<C2Buffer> buffer = C2Buffer::CreateGraphicBuffer(block->share(
             block->crop(), C2Fence{}));
     AString errorDetailMsg;
+    sp<BufferInfosWrapper> infos =
+        new BufferInfosWrapper{decltype(infos->value)()};
+    infos->value.emplace_back(flags, 0 /*not used*/, presentationTimeUs);
     err = codec->queueBuffer(
-            index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
+            index, buffer, infos, tunings, &errorDetailMsg);
     throwExceptionAsNecessary(
             env, err, ACTION_CODE_FATAL,
             codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
@@ -3214,6 +3434,10 @@
         env->GetFieldID(clazz.get(), "mLinearBlock", "Landroid/media/MediaCodec$LinearBlock;");
     CHECK(gFields.outputFrameLinearBlockID != NULL);
 
+    gFields.outputFramebufferInfosID =
+        env->GetFieldID(clazz.get(), "mBufferInfos", "Ljava/util/ArrayDeque;");
+    CHECK(gFields.outputFramebufferInfosID != NULL);
+
     gFields.outputFrameHardwareBufferID =
         env->GetFieldID(clazz.get(), "mHardwareBuffer", "Landroid/hardware/HardwareBuffer;");
     CHECK(gFields.outputFrameHardwareBufferID != NULL);
@@ -3401,6 +3625,19 @@
     gArrayListInfo.addId = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z");
     CHECK(gArrayListInfo.addId != NULL);
 
+    clazz.reset(env->FindClass("java/util/ArrayDeque"));
+    CHECK(clazz.get() != NULL);
+    gArrayDequeInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+    gArrayDequeInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+    CHECK(gArrayDequeInfo.ctorId != NULL);
+
+    gArrayDequeInfo.sizeId = env->GetMethodID(clazz.get(), "size", "()I");
+    CHECK(gArrayDequeInfo.sizeId != NULL);
+
+    gArrayDequeInfo.addId = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z");
+    CHECK(gArrayDequeInfo.addId != NULL);
+
     clazz.reset(env->FindClass("android/media/MediaCodec$LinearBlock"));
     CHECK(clazz.get() != NULL);
 
@@ -3444,6 +3681,12 @@
 
     gBufferInfo.setId = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
     CHECK(gBufferInfo.setId != NULL);
+
+    gFields.bufferInfoSize = env->GetFieldID(clazz.get(), "size", "I");
+    gFields.bufferInfoFlags = env->GetFieldID(clazz.get(), "flags", "I");
+    gFields.bufferInfoOffset = env->GetFieldID(clazz.get(), "offset", "I");
+    gFields.bufferInfoPresentationTimeUs =
+            env->GetFieldID(clazz.get(), "presentationTimeUs", "J");
 }
 
 static void android_media_MediaCodec_native_setup(
@@ -3701,6 +3944,9 @@
     { "native_queueInputBuffer", "(IIIJI)V",
       (void *)android_media_MediaCodec_queueInputBuffer },
 
+    { "native_queueInputBuffers", "(I[Ljava/lang/Object;)V",
+      (void *)android_media_MediaCodec_queueInputBuffers },
+
     { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
       (void *)android_media_MediaCodec_queueSecureInputBuffer },
 
@@ -3711,8 +3957,8 @@
     { "native_closeMediaImage", "(J)V", (void *)android_media_MediaCodec_closeMediaImage },
 
     { "native_queueLinearBlock",
-      "(ILandroid/media/MediaCodec$LinearBlock;IILandroid/media/MediaCodec$CryptoInfo;JI"
-      "Ljava/util/ArrayList;Ljava/util/ArrayList;)V",
+      "(ILandroid/media/MediaCodec$LinearBlock;Landroid/media/MediaCodec$CryptoInfo;"
+      "[Ljava/lang/Object;Ljava/util/ArrayList;Ljava/util/ArrayList;)V",
       (void *)android_media_MediaCodec_native_queueLinearBlock },
 
     { "native_queueHardwareBuffer",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index fbaf64f..02708ef 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -35,6 +35,7 @@
 namespace android {
 
 struct ABuffer;
+struct AccessUnitInfo;
 struct ALooper;
 struct AMessage;
 struct AString;
@@ -93,6 +94,13 @@
             size_t offset, size_t size, int64_t timeUs, uint32_t flags,
             AString *errorDetailMsg);
 
+    status_t queueInputBuffers(
+            size_t index,
+            size_t offset,
+            size_t size,
+            const sp<RefBase> &auInfo,
+            AString *errorDetailMsg = NULL);
+
     status_t queueSecureInputBuffer(
             size_t index,
             size_t offset,
@@ -108,7 +116,7 @@
 
     status_t queueBuffer(
             size_t index, const std::shared_ptr<C2Buffer> &buffer,
-            int64_t timeUs, uint32_t flags, const sp<AMessage> &tunings,
+            const sp<RefBase> &infos, const sp<AMessage> &tunings,
             AString *errorDetailMsg);
 
     status_t queueEncryptedLinearBlock(
@@ -121,8 +129,7 @@
             const uint8_t iv[16],
             CryptoPlugin::Mode mode,
             const CryptoPlugin::Pattern &pattern,
-            int64_t presentationTimeUs,
-            uint32_t flags,
+            const sp<RefBase> &infos,
             const sp<AMessage> &tunings,
             AString *errorDetailMsg);
 
diff --git a/media/jni/soundpool/SoundDecoder.cpp b/media/jni/soundpool/SoundDecoder.cpp
index 5ed10b0..ae57634 100644
--- a/media/jni/soundpool/SoundDecoder.cpp
+++ b/media/jni/soundpool/SoundDecoder.cpp
@@ -29,14 +29,15 @@
 // before the SoundDecoder thread closes.
 static constexpr int32_t kWaitTimeBeforeCloseMs = 1000;
 
-SoundDecoder::SoundDecoder(SoundManager* soundManager, size_t threads)
+SoundDecoder::SoundDecoder(SoundManager* soundManager, size_t threads, int32_t threadPriority)
     : mSoundManager(soundManager)
 {
     ALOGV("%s(%p, %zu)", __func__, soundManager, threads);
     // ThreadPool is created, but we don't launch any threads.
     mThreadPool = std::make_unique<ThreadPool>(
             std::min(threads, (size_t)std::thread::hardware_concurrency()),
-            "SoundDecoder_");
+            "SoundDecoder_",
+            threadPriority);
 }
 
 SoundDecoder::~SoundDecoder()
diff --git a/media/jni/soundpool/SoundDecoder.h b/media/jni/soundpool/SoundDecoder.h
index 7b62114..3f44a0d 100644
--- a/media/jni/soundpool/SoundDecoder.h
+++ b/media/jni/soundpool/SoundDecoder.h
@@ -28,7 +28,7 @@
  */
 class SoundDecoder {
 public:
-    SoundDecoder(SoundManager* soundManager, size_t threads);
+    SoundDecoder(SoundManager* soundManager, size_t threads, int32_t threadPriority);
     ~SoundDecoder();
     void loadSound(int32_t soundID) NO_THREAD_SAFETY_ANALYSIS; // uses unique_lock
     void quit();
diff --git a/media/jni/soundpool/SoundManager.cpp b/media/jni/soundpool/SoundManager.cpp
index 5b16174..fa35813 100644
--- a/media/jni/soundpool/SoundManager.cpp
+++ b/media/jni/soundpool/SoundManager.cpp
@@ -29,7 +29,7 @@
 static const size_t kDecoderThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
 
 SoundManager::SoundManager()
-    : mDecoder{std::make_unique<SoundDecoder>(this, kDecoderThreads)}
+    : mDecoder{std::make_unique<SoundDecoder>(this, kDecoderThreads, ANDROID_PRIORITY_NORMAL)}
 {
     ALOGV("%s()", __func__);
 }
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index 52060f1..e11ccbc 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -35,10 +35,9 @@
 // In R, we change this to true, as it is the correct way per SoundPool documentation.
 static constexpr bool kStealActiveStream_OldestFirst = true;
 
-// kPlayOnCallingThread = true prior to R.
 // Changing to false means calls to play() are almost instantaneous instead of taking around
 // ~10ms to launch the AudioTrack. It is perhaps 100x faster.
-static constexpr bool kPlayOnCallingThread = true;
+static constexpr bool kPlayOnCallingThread = false;
 
 // Amount of time for a StreamManager thread to wait before closing.
 static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
@@ -127,7 +126,8 @@
     mThreadPool = std::make_unique<ThreadPool>(
             std::min((size_t)streams,  // do not make more threads than streams to play
                     std::min(threads, (size_t)std::thread::hardware_concurrency())),
-            "SoundPool_");
+            "SoundPool_",
+            ANDROID_PRIORITY_AUDIO);
 }
 
 #pragma clang diagnostic pop
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
index adbab4b..a4cb286 100644
--- a/media/jni/soundpool/StreamManager.h
+++ b/media/jni/soundpool/StreamManager.h
@@ -46,9 +46,9 @@
  */
 class JavaThread {
 public:
-    JavaThread(std::function<void()> f, const char *name)
+    JavaThread(std::function<void()> f, const char *name, int32_t threadPriority)
         : mF{std::move(f)} {
-        createThreadEtc(staticFunction, this, name);
+        createThreadEtc(staticFunction, this, name, threadPriority);
     }
 
     JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable.
@@ -109,9 +109,11 @@
  */
 class ThreadPool {
 public:
-    ThreadPool(size_t maxThreadCount, std::string name)
+    ThreadPool(size_t maxThreadCount, std::string name,
+            int32_t threadPriority = ANDROID_PRIORITY_NORMAL)
         : mMaxThreadCount(maxThreadCount)
-        , mName{std::move(name)} { }
+        , mName{std::move(name)}
+        , mThreadPriority(threadPriority) {}
 
     ~ThreadPool() { quit(); }
 
@@ -159,7 +161,8 @@
             const int32_t id = mNextThreadId;
             mThreads.emplace_back(std::make_unique<JavaThread>(
                     [this, id, mf = std::move(f)] { mf(id); --mActiveThreadCount; },
-                    (mName + std::to_string(id)).c_str()));
+                    (mName + std::to_string(id)).c_str(),
+                    mThreadPriority));
             ++mActiveThreadCount;
             return id;
         }
@@ -180,6 +183,7 @@
 private:
     const size_t            mMaxThreadCount;
     const std::string       mName;
+    const int32_t           mThreadPriority;
 
     std::atomic_size_t      mActiveThreadCount = 0;
 
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
index 70202463..c18a2de 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
@@ -41,7 +41,7 @@
 
 
     public EffectsTest() {
-        Log.d(TAG, "contructor");
+        Log.d(TAG, "constructor");
     }
 
     @Override
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 10c570b..8ea4632 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -72,6 +72,9 @@
             ],
         },
     },
+    stubs: {
+        symbol_file: "libjnigraphics.map.txt",
+    },
 }
 
 // The headers module is in frameworks/native/Android.bp.
@@ -93,15 +96,18 @@
     ],
     static_libs: ["libarect"],
     fuzz_config: {
-        cc: ["dichenzhang@google.com","scroggo@google.com"],
+        cc: [
+            "dichenzhang@google.com",
+            "scroggo@google.com",
+        ],
         asan_options: [
             "detect_odr_violation=1",
         ],
         hwasan_options: [
-             // Image decoders may attempt to allocate a large amount of memory
-             // (especially if the encoded image is large). This doesn't
-             // necessarily mean there is a bug. Set allocator_may_return_null=1
-             // for hwasan so the fuzzer can continue running.
+            // Image decoders may attempt to allocate a large amount of memory
+            // (especially if the encoded image is large). This doesn't
+            // necessarily mean there is a bug. Set allocator_may_return_null=1
+            // for hwasan so the fuzzer can continue running.
             "allocator_may_return_null = 1",
         ],
     },
diff --git a/nfc-extras/Android.bp b/nfc-extras/Android.bp
index cb9ac6f..1f187e8 100644
--- a/nfc-extras/Android.bp
+++ b/nfc-extras/Android.bp
@@ -23,9 +23,13 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+// TODO(b/303286040): Deprecate this API surface since this is no longer supported (see ag/443092)
 java_sdk_library {
     name: "com.android.nfc_extras",
     srcs: ["java/**/*.java"],
+    libs: [
+        "framework-nfc.impl"
+    ],
     api_packages: ["com.android.nfc_extras"],
     dist_group: "android",
 }
diff --git a/nfc/Android.bp b/nfc/Android.bp
index bf9f47c..2090d339 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -10,7 +10,15 @@
 filegroup {
     name: "framework-nfc-non-updatable-sources",
     path: "java",
-    srcs: [],
+    srcs: [
+        "java/android/nfc/NfcServiceManager.java",
+        "java/android/nfc/cardemulation/ApduServiceInfo.aidl",
+        "java/android/nfc/cardemulation/ApduServiceInfo.java",
+        "java/android/nfc/cardemulation/NfcFServiceInfo.aidl",
+        "java/android/nfc/cardemulation/NfcFServiceInfo.java",
+        "java/android/nfc/cardemulation/AidGroup.aidl",
+        "java/android/nfc/cardemulation/AidGroup.java",
+    ],
 }
 
 filegroup {
@@ -30,10 +38,21 @@
     libs: [
         "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
     ],
+    static_libs: [
+        "android.nfc.flags-aconfig-java",
+    ],
     srcs: [
         ":framework-nfc-updatable-sources",
+        ":framework-nfc-javastream-protos",
     ],
-    defaults: ["framework-non-updatable-unbundled-defaults"],
+    defaults: ["framework-module-defaults"],
+    sdk_version: "module_current",
+    min_sdk_version: "34", // should be 35 (making it 34 for compiling for `-next`)
+    installable: true,
+    optimize: {
+        enabled: false,
+    },
+    hostdex: true, // for hiddenapi check
     permitted_packages: [
         "android.nfc",
         "com.android.nfc",
@@ -41,11 +60,18 @@
     hidden_api_packages: [
         "com.android.nfc",
     ],
-    aidl: {
-        include_dirs: [
-	    // TODO (b/303286040): Remove these when we change to |framework-module-defaults|
-            "frameworks/base/nfc/java",
-            "frameworks/base/core/java",
-        ],
+    impl_library_visibility: [
+        "//frameworks/base:__subpackages__",
+        "//cts/tests/tests/nfc",
+        "//packages/apps/Nfc:__subpackages__",
+    ],
+    jarjar_rules: ":nfc-jarjar-rules",
+    lint: {
+        strict_updatability_linting: true,
     },
 }
+
+filegroup {
+    name: "nfc-jarjar-rules",
+    srcs: ["jarjar-rules.txt"],
+}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index d802177..0ab2ef7 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -1 +1,439 @@
 // Signature format: 2.0
+package android.nfc {
+
+  public final class AvailableNfcAntenna implements android.os.Parcelable {
+    ctor public AvailableNfcAntenna(int, int);
+    method public int describeContents();
+    method public int getLocationX();
+    method public int getLocationY();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.AvailableNfcAntenna> CREATOR;
+  }
+
+  public class FormatException extends java.lang.Exception {
+    ctor public FormatException();
+    ctor public FormatException(String);
+    ctor public FormatException(String, Throwable);
+  }
+
+  public final class NdefMessage implements android.os.Parcelable {
+    ctor public NdefMessage(byte[]) throws android.nfc.FormatException;
+    ctor public NdefMessage(android.nfc.NdefRecord, android.nfc.NdefRecord...);
+    ctor public NdefMessage(android.nfc.NdefRecord[]);
+    method public int describeContents();
+    method public int getByteArrayLength();
+    method public android.nfc.NdefRecord[] getRecords();
+    method public byte[] toByteArray();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefMessage> CREATOR;
+  }
+
+  public final class NdefRecord implements android.os.Parcelable {
+    ctor public NdefRecord(short, byte[], byte[], byte[]);
+    ctor @Deprecated public NdefRecord(byte[]) throws android.nfc.FormatException;
+    method public static android.nfc.NdefRecord createApplicationRecord(String);
+    method public static android.nfc.NdefRecord createExternal(String, String, byte[]);
+    method public static android.nfc.NdefRecord createMime(String, byte[]);
+    method public static android.nfc.NdefRecord createTextRecord(String, String);
+    method public static android.nfc.NdefRecord createUri(android.net.Uri);
+    method public static android.nfc.NdefRecord createUri(String);
+    method public int describeContents();
+    method public byte[] getId();
+    method public byte[] getPayload();
+    method public short getTnf();
+    method public byte[] getType();
+    method @Deprecated public byte[] toByteArray();
+    method public String toMimeType();
+    method public android.net.Uri toUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NdefRecord> CREATOR;
+    field public static final byte[] RTD_ALTERNATIVE_CARRIER;
+    field public static final byte[] RTD_HANDOVER_CARRIER;
+    field public static final byte[] RTD_HANDOVER_REQUEST;
+    field public static final byte[] RTD_HANDOVER_SELECT;
+    field public static final byte[] RTD_SMART_POSTER;
+    field public static final byte[] RTD_TEXT;
+    field public static final byte[] RTD_URI;
+    field public static final short TNF_ABSOLUTE_URI = 3; // 0x3
+    field public static final short TNF_EMPTY = 0; // 0x0
+    field public static final short TNF_EXTERNAL_TYPE = 4; // 0x4
+    field public static final short TNF_MIME_MEDIA = 2; // 0x2
+    field public static final short TNF_UNCHANGED = 6; // 0x6
+    field public static final short TNF_UNKNOWN = 5; // 0x5
+    field public static final short TNF_WELL_KNOWN = 1; // 0x1
+  }
+
+  public final class NfcAdapter {
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction();
+    method public void disableForegroundDispatch(android.app.Activity);
+    method public void disableReaderMode(android.app.Activity);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction();
+    method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
+    method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
+    method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
+    method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
+    method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
+    method public boolean isEnabled();
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
+    method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
+    method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
+    method public boolean isSecureNfcEnabled();
+    method public boolean isSecureNfcSupported();
+    method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
+    method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
+    field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
+    field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
+    field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
+    field public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
+    field public static final String ACTION_TECH_DISCOVERED = "android.nfc.action.TECH_DISCOVERED";
+    field @RequiresPermission(android.Manifest.permission.NFC_TRANSACTION_EVENT) public static final String ACTION_TRANSACTION_DETECTED = "android.nfc.action.TRANSACTION_DETECTED";
+    field public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
+    field public static final String EXTRA_AID = "android.nfc.extra.AID";
+    field public static final String EXTRA_DATA = "android.nfc.extra.DATA";
+    field public static final String EXTRA_ID = "android.nfc.extra.ID";
+    field public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
+    field public static final String EXTRA_PREFERRED_PAYMENT_CHANGED_REASON = "android.nfc.extra.PREFERRED_PAYMENT_CHANGED_REASON";
+    field public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
+    field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
+    field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_DISABLE = 0; // 0x0
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -2147483648; // 0x80000000
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_A = 1; // 0x1
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_B = 2; // 0x2
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_F = 4; // 0x4
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_DISABLE = 0; // 0x0
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -2147483648; // 0x80000000
+    field public static final int FLAG_READER_NFC_A = 1; // 0x1
+    field public static final int FLAG_READER_NFC_B = 2; // 0x2
+    field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
+    field public static final int FLAG_READER_NFC_F = 4; // 0x4
+    field public static final int FLAG_READER_NFC_V = 8; // 0x8
+    field public static final int FLAG_READER_NO_PLATFORM_SOUNDS = 256; // 0x100
+    field public static final int FLAG_READER_SKIP_NDEF_CHECK = 128; // 0x80
+    field public static final int PREFERRED_PAYMENT_CHANGED = 2; // 0x2
+    field public static final int PREFERRED_PAYMENT_LOADED = 1; // 0x1
+    field public static final int PREFERRED_PAYMENT_UPDATED = 3; // 0x3
+    field public static final int STATE_OFF = 1; // 0x1
+    field public static final int STATE_ON = 3; // 0x3
+    field public static final int STATE_TURNING_OFF = 4; // 0x4
+    field public static final int STATE_TURNING_ON = 2; // 0x2
+  }
+
+  @Deprecated public static interface NfcAdapter.CreateBeamUrisCallback {
+    method @Deprecated public android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
+  }
+
+  @Deprecated public static interface NfcAdapter.CreateNdefMessageCallback {
+    method @Deprecated public android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
+  }
+
+  @Deprecated public static interface NfcAdapter.OnNdefPushCompleteCallback {
+    method @Deprecated public void onNdefPushComplete(android.nfc.NfcEvent);
+  }
+
+  public static interface NfcAdapter.OnTagRemovedListener {
+    method public void onTagRemoved();
+  }
+
+  public static interface NfcAdapter.ReaderCallback {
+    method public void onTagDiscovered(android.nfc.Tag);
+  }
+
+  public final class NfcAntennaInfo implements android.os.Parcelable {
+    ctor public NfcAntennaInfo(int, int, boolean, @NonNull java.util.List<android.nfc.AvailableNfcAntenna>);
+    method public int describeContents();
+    method @NonNull public java.util.List<android.nfc.AvailableNfcAntenna> getAvailableNfcAntennas();
+    method public int getDeviceHeight();
+    method public int getDeviceWidth();
+    method public boolean isDeviceFoldable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.NfcAntennaInfo> CREATOR;
+  }
+
+  public final class NfcEvent {
+    field public final android.nfc.NfcAdapter nfcAdapter;
+    field public final int peerLlcpMajorVersion;
+    field public final int peerLlcpMinorVersion;
+  }
+
+  public final class NfcManager {
+    method public android.nfc.NfcAdapter getDefaultAdapter();
+  }
+
+  public final class Tag implements android.os.Parcelable {
+    method public int describeContents();
+    method public byte[] getId();
+    method public String[] getTechList();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.nfc.Tag> CREATOR;
+  }
+
+  public class TagLostException extends java.io.IOException {
+    ctor public TagLostException();
+    ctor public TagLostException(String);
+  }
+
+}
+
+package android.nfc.cardemulation {
+
+  public final class CardEmulation {
+    method public boolean categoryAllowsForegroundPreference(String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
+    method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+    method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
+    method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+    method public int getSelectionModeForCategory(String);
+    method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
+    method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
+    method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+    method public boolean removeAidsForService(android.content.ComponentName, String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
+    method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
+    method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean);
+    method public boolean supportsAidPrefixRegistration();
+    method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
+    method public boolean unsetPreferredService(android.app.Activity);
+    field public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
+    field public static final String CATEGORY_OTHER = "other";
+    field public static final String CATEGORY_PAYMENT = "payment";
+    field public static final String EXTRA_CATEGORY = "category";
+    field public static final String EXTRA_SERVICE_COMPONENT = "component";
+    field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
+    field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
+    field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
+  }
+
+  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 public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
+    method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
+    method public final void sendResponseApdu(byte[]);
+    field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
+    field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
+    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
+    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
+  }
+
+  public abstract class HostNfcFService extends android.app.Service {
+    ctor public HostNfcFService();
+    method public final android.os.IBinder onBind(android.content.Intent);
+    method public abstract void onDeactivated(int);
+    method public abstract byte[] processNfcFPacket(byte[], android.os.Bundle);
+    method public final void sendResponsePacket(byte[]);
+    field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
+    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_NFCF_SERVICE";
+    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_nfcf_service";
+  }
+
+  public final class NfcFCardEmulation {
+    method public boolean disableService(android.app.Activity) throws java.lang.RuntimeException;
+    method public boolean enableService(android.app.Activity, android.content.ComponentName) throws java.lang.RuntimeException;
+    method public static android.nfc.cardemulation.NfcFCardEmulation getInstance(android.nfc.NfcAdapter);
+    method public String getNfcid2ForService(android.content.ComponentName) throws java.lang.RuntimeException;
+    method public String getSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
+    method public boolean registerSystemCodeForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
+    method public boolean setNfcid2ForService(android.content.ComponentName, String) throws java.lang.RuntimeException;
+    method public boolean unregisterSystemCodeForService(android.content.ComponentName) throws java.lang.RuntimeException;
+  }
+
+  public abstract class OffHostApduService extends android.app.Service {
+    ctor public OffHostApduService();
+    field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
+    field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
+  }
+
+}
+
+package android.nfc.tech {
+
+  public final class IsoDep implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.IsoDep get(android.nfc.Tag);
+    method public byte[] getHiLayerResponse();
+    method public byte[] getHistoricalBytes();
+    method public int getMaxTransceiveLength();
+    method public android.nfc.Tag getTag();
+    method public int getTimeout();
+    method public boolean isConnected();
+    method public boolean isExtendedLengthApduSupported();
+    method public void setTimeout(int);
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+  }
+
+  public final class MifareClassic implements android.nfc.tech.TagTechnology {
+    method public boolean authenticateSectorWithKeyA(int, byte[]) throws java.io.IOException;
+    method public boolean authenticateSectorWithKeyB(int, byte[]) throws java.io.IOException;
+    method public int blockToSector(int);
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public void decrement(int, int) throws java.io.IOException;
+    method public static android.nfc.tech.MifareClassic get(android.nfc.Tag);
+    method public int getBlockCount();
+    method public int getBlockCountInSector(int);
+    method public int getMaxTransceiveLength();
+    method public int getSectorCount();
+    method public int getSize();
+    method public android.nfc.Tag getTag();
+    method public int getTimeout();
+    method public int getType();
+    method public void increment(int, int) throws java.io.IOException;
+    method public boolean isConnected();
+    method public byte[] readBlock(int) throws java.io.IOException;
+    method public void restore(int) throws java.io.IOException;
+    method public int sectorToBlock(int);
+    method public void setTimeout(int);
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+    method public void transfer(int) throws java.io.IOException;
+    method public void writeBlock(int, byte[]) throws java.io.IOException;
+    field public static final int BLOCK_SIZE = 16; // 0x10
+    field public static final byte[] KEY_DEFAULT;
+    field public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY;
+    field public static final byte[] KEY_NFC_FORUM;
+    field public static final int SIZE_1K = 1024; // 0x400
+    field public static final int SIZE_2K = 2048; // 0x800
+    field public static final int SIZE_4K = 4096; // 0x1000
+    field public static final int SIZE_MINI = 320; // 0x140
+    field public static final int TYPE_CLASSIC = 0; // 0x0
+    field public static final int TYPE_PLUS = 1; // 0x1
+    field public static final int TYPE_PRO = 2; // 0x2
+    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public final class MifareUltralight implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.MifareUltralight get(android.nfc.Tag);
+    method public int getMaxTransceiveLength();
+    method public android.nfc.Tag getTag();
+    method public int getTimeout();
+    method public int getType();
+    method public boolean isConnected();
+    method public byte[] readPages(int) throws java.io.IOException;
+    method public void setTimeout(int);
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+    method public void writePage(int, byte[]) throws java.io.IOException;
+    field public static final int PAGE_SIZE = 4; // 0x4
+    field public static final int TYPE_ULTRALIGHT = 1; // 0x1
+    field public static final int TYPE_ULTRALIGHT_C = 2; // 0x2
+    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public final class Ndef implements android.nfc.tech.TagTechnology {
+    method public boolean canMakeReadOnly();
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.Ndef get(android.nfc.Tag);
+    method public android.nfc.NdefMessage getCachedNdefMessage();
+    method public int getMaxSize();
+    method public android.nfc.NdefMessage getNdefMessage() throws android.nfc.FormatException, java.io.IOException;
+    method public android.nfc.Tag getTag();
+    method public String getType();
+    method public boolean isConnected();
+    method public boolean isWritable();
+    method public boolean makeReadOnly() throws java.io.IOException;
+    method public void writeNdefMessage(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+    field public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
+    field public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
+    field public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
+    field public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
+    field public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
+  }
+
+  public final class NdefFormatable implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public void format(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+    method public void formatReadOnly(android.nfc.NdefMessage) throws android.nfc.FormatException, java.io.IOException;
+    method public static android.nfc.tech.NdefFormatable get(android.nfc.Tag);
+    method public android.nfc.Tag getTag();
+    method public boolean isConnected();
+  }
+
+  public final class NfcA implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.NfcA get(android.nfc.Tag);
+    method public byte[] getAtqa();
+    method public int getMaxTransceiveLength();
+    method public short getSak();
+    method public android.nfc.Tag getTag();
+    method public int getTimeout();
+    method public boolean isConnected();
+    method public void setTimeout(int);
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+  }
+
+  public final class NfcB implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.NfcB get(android.nfc.Tag);
+    method public byte[] getApplicationData();
+    method public int getMaxTransceiveLength();
+    method public byte[] getProtocolInfo();
+    method public android.nfc.Tag getTag();
+    method public boolean isConnected();
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+  }
+
+  public final class NfcBarcode implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.NfcBarcode get(android.nfc.Tag);
+    method public byte[] getBarcode();
+    method public android.nfc.Tag getTag();
+    method public int getType();
+    method public boolean isConnected();
+    field public static final int TYPE_KOVIO = 1; // 0x1
+    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public final class NfcF implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.NfcF get(android.nfc.Tag);
+    method public byte[] getManufacturer();
+    method public int getMaxTransceiveLength();
+    method public byte[] getSystemCode();
+    method public android.nfc.Tag getTag();
+    method public int getTimeout();
+    method public boolean isConnected();
+    method public void setTimeout(int);
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+  }
+
+  public final class NfcV implements android.nfc.tech.TagTechnology {
+    method public void close() throws java.io.IOException;
+    method public void connect() throws java.io.IOException;
+    method public static android.nfc.tech.NfcV get(android.nfc.Tag);
+    method public byte getDsfId();
+    method public int getMaxTransceiveLength();
+    method public byte getResponseFlags();
+    method public android.nfc.Tag getTag();
+    method public boolean isConnected();
+    method public byte[] transceive(byte[]) throws java.io.IOException;
+  }
+
+  public interface TagTechnology extends java.io.Closeable {
+    method public void connect() throws java.io.IOException;
+    method public android.nfc.Tag getTag();
+    method public boolean isConnected();
+  }
+
+}
+
diff --git a/nfc/api/lint-baseline.txt b/nfc/api/lint-baseline.txt
new file mode 100644
index 0000000..ef9aab6
--- /dev/null
+++ b/nfc/api/lint-baseline.txt
@@ -0,0 +1,95 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+    Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
+    Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+    Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
+
+
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent):
+    Missing nullability on method `onBind` return
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onBind`
+
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+    Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+    Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+    Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+    Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+    Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+    Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+    Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+    Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+    Method 'increment' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+    Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+    Method 'restore' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+    Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+    Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+    Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+    Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+    Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+    Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+    Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+    Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+    Method 'format' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+    Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+    Method 'close' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+    Method 'connect' documentation mentions permissions without declaring @RequiresPermission
diff --git a/nfc/api/module-lib-current.txt b/nfc/api/module-lib-current.txt
index d802177..5ebe911 100644
--- a/nfc/api/module-lib-current.txt
+++ b/nfc/api/module-lib-current.txt
@@ -1 +1,10 @@
 // Signature format: 2.0
+package android.nfc {
+
+  public class NfcFrameworkInitializer {
+    method public static void registerServiceWrappers();
+    method public static void setNfcServiceManager(@NonNull android.nfc.NfcServiceManager);
+  }
+
+}
+
diff --git a/nfc/api/module-lib-lint-baseline.txt b/nfc/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..f7f8ee3
--- /dev/null
+++ b/nfc/api/module-lib-lint-baseline.txt
@@ -0,0 +1,93 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+    Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
+    Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+    Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+    Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+    Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+    Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+    Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+    Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+    Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+    Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+    Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+    Method 'increment' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+    Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+    Method 'restore' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+    Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+    Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+    Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+    Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+    Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+    Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+    Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+    Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+    Method 'format' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+    Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+    Method 'close' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+    Method 'connect' documentation mentions permissions without declaring @RequiresPermission
+
+SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/nfc/api/removed.txt b/nfc/api/removed.txt
index d802177..fb82b5d 100644
--- a/nfc/api/removed.txt
+++ b/nfc/api/removed.txt
@@ -1 +1,17 @@
 // Signature format: 2.0
+package android.nfc {
+
+  public final class NfcAdapter {
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void disableForegroundNdefPush(android.app.Activity);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public boolean invokeBeam(android.app.Activity);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public boolean isNdefPushEnabled();
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setBeamPushUris(android.net.Uri[], android.app.Activity);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
+    method @Deprecated @android.compat.annotation.UnsupportedAppUsage public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
+  }
+
+}
+
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index d802177..d5b3c7d 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -1 +1,45 @@
 // Signature format: 2.0
+package android.nfc {
+
+  public final class NfcAdapter {
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
+    method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
+    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+    field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
+    field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
+    field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
+    field public static final int TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE = -2; // 0xfffffffe
+  }
+
+  public static interface NfcAdapter.ControllerAlwaysOnListener {
+    method public void onControllerAlwaysOnChanged(boolean);
+  }
+
+  public static interface NfcAdapter.NfcUnlockHandler {
+    method public boolean onUnlockAttempted(android.nfc.Tag);
+  }
+
+}
+
+package android.nfc.cardemulation {
+
+  public final class CardEmulation {
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
+  }
+
+}
+
diff --git a/nfc/api/system-lint-baseline.txt b/nfc/api/system-lint-baseline.txt
new file mode 100644
index 0000000..761c8e6
--- /dev/null
+++ b/nfc/api/system-lint-baseline.txt
@@ -0,0 +1,105 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+    Field 'ACTION_ADAPTER_STATE_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_PREFERRED_PAYMENT_CHANGED:
+    Field 'ACTION_PREFERRED_PAYMENT_CHANGED' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @BroadcastBehavior
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+    Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
+
+
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent):
+    Missing nullability on method `onBind` return
+MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent) parameter #0:
+    Missing nullability on parameter `intent` in method `onBind`
+
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+    Method 'disableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+    Method 'enableForegroundDispatch' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+    Method 'isDefaultServiceForAid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+    Method 'isDefaultServiceForCategory' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+    Method 'setOffHostForService' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+    Method 'authenticateSectorWithKeyA' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+    Method 'authenticateSectorWithKeyB' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+    Method 'decrement' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+    Method 'increment' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+    Method 'readBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+    Method 'restore' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+    Method 'transfer' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+    Method 'writeBlock' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+    Method 'readPages' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+    Method 'writePage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+    Method 'getNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+    Method 'isWritable' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+    Method 'makeReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+    Method 'writeNdefMessage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+    Method 'format' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+    Method 'formatReadOnly' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+    Method 'getTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+    Method 'setTimeout' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+    Method 'transceive' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+    Method 'close' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+    Method 'connect' documentation mentions permissions without declaring @RequiresPermission
+
+SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
+    SAM-compatible parameters (such as parameter 2, "callback", in android.nfc.NfcAdapter.enableReaderMode) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 3, "tagRemovedListener", in android.nfc.NfcAdapter.ignore) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
+SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
+    Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/nfc/api/system-removed.txt b/nfc/api/system-removed.txt
index d802177..c6eaa57 100644
--- a/nfc/api/system-removed.txt
+++ b/nfc/api/system-removed.txt
@@ -1 +1,12 @@
 // Signature format: 2.0
+package android.nfc {
+
+  public final class NfcAdapter {
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disableNdefPush();
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
+    method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
+    field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
+  }
+
+}
+
diff --git a/nfc/jarjar-rules.txt b/nfc/jarjar-rules.txt
new file mode 100644
index 0000000..4cd652d
--- /dev/null
+++ b/nfc/jarjar-rules.txt
@@ -0,0 +1,38 @@
+# Used by framework-nfc for proto debug dumping
+rule android.app.PendingIntentProto* com.android.nfc.x.@0
+rule android.content.ComponentNameProto* com.android.nfc.x.@0
+rule android.content.IntentProto* com.android.nfc.x.@0
+rule android.content.IntentFilterProto* com.android.nfc.x.@0
+rule android.content.AuthorityEntryProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.AidGroupProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.ApduServiceInfoProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.NfcFServiceInfoProto* com.android.nfc.x.@0
+rule android.nfc.NdefMessageProto* com.android.nfc.x.@0
+rule android.nfc.NdefRecordProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.CardEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredServicesCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredNfcFServicesCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.PreferredServicesProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.EnabledNfcFServicesProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredAidCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.AidRoutingManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredT3tIdentifiersCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.SystemCodeRoutingManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.HostEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.HostNfcFEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.NfcServiceDumpProto* com.android.nfc.x.@0
+rule com.android.nfc.DiscoveryParamsProto* com.android.nfc.x.@0
+rule com.android.nfc.NfcDispatcherProto* com.android.nfc.x.@0
+rule android.os.PersistableBundleProto* com.android.nfc.x.@0
+
+# Used by framework-nfc for reading trunk stable flags
+rule android.nfc.FakeFeatureFlagsImpl* com.android.nfc.x.@0
+rule android.nfc.FeatureFlags* com.android.nfc.x.@0
+rule android.nfc.Flags* com.android.nfc.x.@0
+rule android.permission.flags.** com.android.nfc.x.@0
+
+# Used by framework-nfc for misc utilities
+rule android.os.PatternMatcher* com.android.nfc.x.@0
+
+rule com.android.incident.Privacy* com.android.nfc.x.@0
+rule com.android.incident.PrivacyFlags* com.android.nfc.x.@0
diff --git a/core/java/android/nfc/ApduList.aidl b/nfc/java/android/nfc/ApduList.aidl
similarity index 100%
rename from core/java/android/nfc/ApduList.aidl
rename to nfc/java/android/nfc/ApduList.aidl
diff --git a/core/java/android/nfc/ApduList.java b/nfc/java/android/nfc/ApduList.java
similarity index 100%
rename from core/java/android/nfc/ApduList.java
rename to nfc/java/android/nfc/ApduList.java
diff --git a/core/java/android/nfc/AvailableNfcAntenna.aidl b/nfc/java/android/nfc/AvailableNfcAntenna.aidl
similarity index 100%
rename from core/java/android/nfc/AvailableNfcAntenna.aidl
rename to nfc/java/android/nfc/AvailableNfcAntenna.aidl
diff --git a/core/java/android/nfc/AvailableNfcAntenna.java b/nfc/java/android/nfc/AvailableNfcAntenna.java
similarity index 100%
rename from core/java/android/nfc/AvailableNfcAntenna.java
rename to nfc/java/android/nfc/AvailableNfcAntenna.java
diff --git a/core/java/android/nfc/Constants.java b/nfc/java/android/nfc/Constants.java
similarity index 100%
rename from core/java/android/nfc/Constants.java
rename to nfc/java/android/nfc/Constants.java
diff --git a/core/java/android/nfc/ErrorCodes.java b/nfc/java/android/nfc/ErrorCodes.java
similarity index 100%
rename from core/java/android/nfc/ErrorCodes.java
rename to nfc/java/android/nfc/ErrorCodes.java
diff --git a/core/java/android/nfc/FormatException.java b/nfc/java/android/nfc/FormatException.java
similarity index 100%
rename from core/java/android/nfc/FormatException.java
rename to nfc/java/android/nfc/FormatException.java
diff --git a/core/java/android/nfc/IAppCallback.aidl b/nfc/java/android/nfc/IAppCallback.aidl
similarity index 100%
rename from core/java/android/nfc/IAppCallback.aidl
rename to nfc/java/android/nfc/IAppCallback.aidl
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
similarity index 97%
rename from core/java/android/nfc/INfcAdapter.aidl
rename to nfc/java/android/nfc/INfcAdapter.aidl
index f6beec1..85879ac 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -86,4 +86,5 @@
     boolean enableReaderOption(boolean enable);
     boolean isObserveModeSupported();
     boolean setObserveMode(boolean enabled);
+    void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
 }
diff --git a/core/java/android/nfc/INfcAdapterExtras.aidl b/nfc/java/android/nfc/INfcAdapterExtras.aidl
similarity index 100%
rename from core/java/android/nfc/INfcAdapterExtras.aidl
rename to nfc/java/android/nfc/INfcAdapterExtras.aidl
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
similarity index 94%
rename from core/java/android/nfc/INfcCardEmulation.aidl
rename to nfc/java/android/nfc/INfcCardEmulation.aidl
index 191385a..f4b4604 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -43,4 +43,7 @@
     ApduServiceInfo getPreferredPaymentService(int userHandle);
     boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
     boolean isDefaultPaymentRegistered();
+
+    boolean overrideRoutingTable(int userHandle, String protocol, String technology);
+    boolean recoverRoutingTable(int userHandle);
 }
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl b/nfc/java/android/nfc/INfcControllerAlwaysOnListener.aidl
similarity index 100%
rename from core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
rename to nfc/java/android/nfc/INfcControllerAlwaysOnListener.aidl
diff --git a/core/java/android/nfc/INfcDta.aidl b/nfc/java/android/nfc/INfcDta.aidl
similarity index 100%
rename from core/java/android/nfc/INfcDta.aidl
rename to nfc/java/android/nfc/INfcDta.aidl
diff --git a/core/java/android/nfc/INfcFCardEmulation.aidl b/nfc/java/android/nfc/INfcFCardEmulation.aidl
similarity index 100%
rename from core/java/android/nfc/INfcFCardEmulation.aidl
rename to nfc/java/android/nfc/INfcFCardEmulation.aidl
diff --git a/core/java/android/nfc/INfcTag.aidl b/nfc/java/android/nfc/INfcTag.aidl
similarity index 100%
rename from core/java/android/nfc/INfcTag.aidl
rename to nfc/java/android/nfc/INfcTag.aidl
diff --git a/core/java/android/nfc/INfcUnlockHandler.aidl b/nfc/java/android/nfc/INfcUnlockHandler.aidl
similarity index 100%
rename from core/java/android/nfc/INfcUnlockHandler.aidl
rename to nfc/java/android/nfc/INfcUnlockHandler.aidl
diff --git a/core/java/android/nfc/ITagRemovedCallback.aidl b/nfc/java/android/nfc/ITagRemovedCallback.aidl
similarity index 100%
rename from core/java/android/nfc/ITagRemovedCallback.aidl
rename to nfc/java/android/nfc/ITagRemovedCallback.aidl
diff --git a/core/java/android/nfc/NdefMessage.aidl b/nfc/java/android/nfc/NdefMessage.aidl
similarity index 100%
rename from core/java/android/nfc/NdefMessage.aidl
rename to nfc/java/android/nfc/NdefMessage.aidl
diff --git a/core/java/android/nfc/NdefMessage.java b/nfc/java/android/nfc/NdefMessage.java
similarity index 100%
rename from core/java/android/nfc/NdefMessage.java
rename to nfc/java/android/nfc/NdefMessage.java
diff --git a/core/java/android/nfc/NdefRecord.aidl b/nfc/java/android/nfc/NdefRecord.aidl
similarity index 100%
rename from core/java/android/nfc/NdefRecord.aidl
rename to nfc/java/android/nfc/NdefRecord.aidl
diff --git a/core/java/android/nfc/NdefRecord.java b/nfc/java/android/nfc/NdefRecord.java
similarity index 100%
rename from core/java/android/nfc/NdefRecord.java
rename to nfc/java/android/nfc/NdefRecord.java
diff --git a/core/java/android/nfc/NfcActivityManager.java b/nfc/java/android/nfc/NfcActivityManager.java
similarity index 79%
rename from core/java/android/nfc/NfcActivityManager.java
rename to nfc/java/android/nfc/NfcActivityManager.java
index 8d75cac..f03fc0a 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/nfc/java/android/nfc/NfcActivityManager.java
@@ -112,6 +112,9 @@
         Bundle readerModeExtras = null;
         Binder token;
 
+        int mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+        int mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
+
         public NfcActivityState(Activity activity) {
             if (activity.isDestroyed()) {
                 throw new IllegalStateException("activity is already destroyed");
@@ -132,6 +135,9 @@
             readerModeFlags = 0;
             readerModeExtras = null;
             token = null;
+
+            mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+            mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
         }
         @Override
         public String toString() {
@@ -278,6 +284,9 @@
         int readerModeFlags = 0;
         Bundle readerModeExtras = null;
         Binder token;
+        int pollTech;
+        int listenTech;
+
         synchronized (NfcActivityManager.this) {
             NfcActivityState state = findActivityState(activity);
             if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
@@ -286,9 +295,15 @@
             token = state.token;
             readerModeFlags = state.readerModeFlags;
             readerModeExtras = state.readerModeExtras;
+
+            pollTech = state.mPollTech;
+            listenTech = state.mListenTech;
         }
         if (readerModeFlags != 0) {
             setReaderMode(token, readerModeFlags, readerModeExtras);
+        } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
+                || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
+            changeDiscoveryTech(token, pollTech, listenTech);
         }
         requestNfcServiceCallback();
     }
@@ -298,6 +313,9 @@
     public void onActivityPaused(Activity activity) {
         boolean readerModeFlagsSet;
         Binder token;
+        int pollTech;
+        int listenTech;
+
         synchronized (NfcActivityManager.this) {
             NfcActivityState state = findActivityState(activity);
             if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
@@ -305,10 +323,17 @@
             state.resumed = false;
             token = state.token;
             readerModeFlagsSet = state.readerModeFlags != 0;
+
+            pollTech = state.mPollTech;
+            listenTech = state.mListenTech;
         }
         if (readerModeFlagsSet) {
             // Restore default p2p modes
             setReaderMode(token, 0, null);
+        } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
+                || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
+            changeDiscoveryTech(token,
+                    NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
         }
     }
 
@@ -333,4 +358,53 @@
         }
     }
 
+    /** setDiscoveryTechnology() implementation */
+    public void setDiscoveryTech(Activity activity, int pollTech, int listenTech) {
+        boolean isResumed;
+        Binder token;
+        boolean readerModeFlagsSet;
+        synchronized (NfcActivityManager.this) {
+            NfcActivityState state = getActivityState(activity);
+            readerModeFlagsSet = state.readerModeFlags != 0;
+            state.mListenTech = listenTech;
+            state.mPollTech = pollTech;
+            token = state.token;
+            isResumed = state.resumed;
+        }
+        if (!readerModeFlagsSet && isResumed) {
+            changeDiscoveryTech(token, pollTech, listenTech);
+        } else if (readerModeFlagsSet) {
+            throw new IllegalStateException("Cannot be used when the Reader Mode is enabled");
+        }
+    }
+
+    /** resetDiscoveryTechnology() implementation */
+    public void resetDiscoveryTech(Activity activity) {
+        boolean isResumed;
+        Binder token;
+        boolean readerModeFlagsSet;
+        synchronized (NfcActivityManager.this) {
+            NfcActivityState state = getActivityState(activity);
+            readerModeFlagsSet = state.readerModeFlags != 0;
+            state.mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
+            state.mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+            token = state.token;
+            isResumed = state.resumed;
+        }
+        if (readerModeFlagsSet) {
+            disableReaderMode(activity);
+        } else if (isResumed) {
+            changeDiscoveryTech(token, NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
+        }
+
+    }
+
+    private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) {
+        try {
+            NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech);
+        } catch (RemoteException e) {
+            mAdapter.attemptDeadServiceRecovery(e);
+        }
+    }
+
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
similarity index 93%
rename from core/java/android/nfc/NfcAdapter.java
rename to nfc/java/android/nfc/NfcAdapter.java
index f407fb7..0791721 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -332,6 +332,19 @@
      */
     public static final int FLAG_READER_NFC_BARCODE = 0x10;
 
+    /** @hide */
+    @IntDef(flag = true, prefix = {"FLAG_READER_"}, value = {
+        FLAG_READER_KEEP,
+        FLAG_READER_DISABLE,
+        FLAG_READER_NFC_A,
+        FLAG_READER_NFC_B,
+        FLAG_READER_NFC_F,
+        FLAG_READER_NFC_V,
+        FLAG_READER_NFC_BARCODE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PollTechnology {}
+
     /**
      * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
      * <p>
@@ -359,6 +372,76 @@
     public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
 
     /**
+     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag enables listening for Nfc-A technology.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_NFC_PASSIVE_A = 0x1;
+
+    /**
+     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag enables listening for Nfc-B technology.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_NFC_PASSIVE_B = 1 << 1;
+
+    /**
+     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag enables listening for Nfc-F technology.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_NFC_PASSIVE_F = 1 << 2;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag disables listening.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_DISABLE = 0x0;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag disables polling.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_READER_DISABLE = 0x0;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag makes listening to keep the current technology configuration.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_KEEP = 0x80000000;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag makes polling to keep the current technology configuration.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_READER_KEEP = 0x80000000;
+
+    /** @hide */
+    public static final int FLAG_USE_ALL_TECH = 0xff;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"FLAG_LISTEN_"}, value = {
+        FLAG_LISTEN_KEEP,
+        FLAG_LISTEN_DISABLE,
+        FLAG_LISTEN_NFC_PASSIVE_A,
+        FLAG_LISTEN_NFC_PASSIVE_B,
+        FLAG_LISTEN_NFC_PASSIVE_F
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ListenTechnology {}
+
+    /**
      * @hide
      * @removed
      */
@@ -436,11 +519,13 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface TagIntentAppPreferenceResult {}
 
-    // Guarded by NfcAdapter.class
+    // Guarded by sLock
     static boolean sIsInitialized = false;
     static boolean sHasNfcFeature;
     static boolean sHasCeFeature;
 
+    static Object sLock = new Object();
+
     // Final after first constructor, except for
     // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
     // recovery
@@ -1230,7 +1315,7 @@
     @java.lang.Deprecated
     @UnsupportedAppUsage
     public void setBeamPushUris(Uri[] uris, Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1300,7 +1385,7 @@
     @java.lang.Deprecated
     @UnsupportedAppUsage
     public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1385,7 +1470,7 @@
     @UnsupportedAppUsage
     public void setNdefPushMessage(NdefMessage message, Activity activity,
             Activity ... activities) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1399,7 +1484,7 @@
     @SystemApi
     @UnsupportedAppUsage
     public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1478,7 +1563,7 @@
     @UnsupportedAppUsage
     public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
             Activity ... activities) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1529,7 +1614,7 @@
     @UnsupportedAppUsage
     public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
             Activity activity, Activity ... activities) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1572,7 +1657,7 @@
      */
     public void enableForegroundDispatch(Activity activity, PendingIntent intent,
             IntentFilter[] filters, String[][] techLists) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1607,7 +1692,7 @@
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void disableForegroundDispatch(Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1643,7 +1728,7 @@
      */
     public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
             Bundle extras) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1660,7 +1745,7 @@
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void disableReaderMode(Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1688,7 +1773,7 @@
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     @SuppressLint("VisiblySynchronized")
     public void setReaderMode(boolean enablePolling) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1703,6 +1788,91 @@
     }
 
     /**
+     * Set the NFC controller to enable specific poll/listen technologies,
+     * as specified in parameters, while this Activity is in the foreground.
+     *
+     * Use {@link #FLAG_READER_KEEP} to keep current polling technology.
+     * Use {@link #FLAG_LISTEN_KEEP} to keep current listenig technology.
+     * (if the _KEEP flag is specified the other technology flags shouldn't be set
+     * and are quietly ignored otherwise).
+     * Use {@link #FLAG_READER_DISABLE} to disable polling.
+     * Use {@link #FLAG_LISTEN_DISABLE} to disable listening.
+     * Also refer to {@link #resetDiscoveryTechnology(Activity)} to restore these changes.
+     * </p>
+     * The pollTechnology, listenTechnology parameters can be one or several of below list.
+     * <pre>
+     *                    Poll                    Listen
+     *  Passive A         0x01   (NFC_A)           0x01  (NFC_PASSIVE_A)
+     *  Passive B         0x02   (NFC_B)           0x02  (NFC_PASSIVE_B)
+     *  Passive F         0x04   (NFC_F)           0x04  (NFC_PASSIVE_F)
+     *  ISO 15693         0x08   (NFC_V)             -
+     *  Kovio             0x10   (NFC_BARCODE)       -
+     * </pre>
+     * <p>Example usage in an Activity that requires to disable poll,
+     * keep current listen technologies:
+     * <pre>
+     * protected void onResume() {
+     *     mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
+     *     mNfcAdapter.setDiscoveryTechnology(this,
+     *         NfcAdapter.FLAG_READER_DISABLE, NfcAdapter.FLAG_LISTEN_KEEP);
+     * }</pre></p>
+     * @param activity The Activity that requests NFC controller to enable specific technologies.
+     * @param pollTechnology Flags indicating poll technologies.
+     * @param listenTechnology Flags indicating listen technologies.
+     * @throws UnsupportedOperationException if FEATURE_NFC,
+     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF are unavailable.
+     */
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public void setDiscoveryTechnology(@NonNull Activity activity,
+            @PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) {
+
+        // A special treatment of the _KEEP flags
+        if ((listenTechnology & FLAG_LISTEN_KEEP) != 0) {
+            listenTechnology = -1;
+        }
+        if ((pollTechnology & FLAG_READER_KEEP) != 0) {
+            pollTechnology = -1;
+        }
+
+        if (listenTechnology == FLAG_LISTEN_DISABLE) {
+            synchronized (sLock) {
+                if (!sHasNfcFeature) {
+                    throw new UnsupportedOperationException();
+                }
+            }
+            mNfcActivityManager.enableReaderMode(activity, null, pollTechnology, null);
+            return;
+        }
+        if (pollTechnology == FLAG_READER_DISABLE) {
+            synchronized (sLock) {
+                if (!sHasCeFeature) {
+                    throw new UnsupportedOperationException();
+                }
+            }
+        } else {
+            synchronized (sLock) {
+                if (!sHasNfcFeature || !sHasCeFeature) {
+                    throw new UnsupportedOperationException();
+                }
+            }
+        }
+        mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
+    }
+
+    /**
+     * Restore the poll/listen technologies of NFC controller to its default state,
+     * which were changed by {@link #setDiscoveryTechnology(Activity , int , int)}
+     *
+     * @param activity The Activity that requested to change technologies.
+     */
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public void resetDiscoveryTechnology(@NonNull Activity activity) {
+        mNfcActivityManager.resetDiscoveryTech(activity);
+    }
+
+    /**
      * Manually invoke Android Beam to share data.
      *
      * <p>The Android Beam animation is normally only shown when two NFC-capable
@@ -1732,7 +1902,7 @@
     @java.lang.Deprecated
     @UnsupportedAppUsage
     public boolean invokeBeam(Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1770,7 +1940,7 @@
     @Deprecated
     @UnsupportedAppUsage
     public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1800,7 +1970,7 @@
     @Deprecated
     @UnsupportedAppUsage
     public void disableForegroundNdefPush(Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -2080,7 +2250,7 @@
     @java.lang.Deprecated
     @UnsupportedAppUsage
     public boolean isNdefPushEnabled() {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -2194,7 +2364,7 @@
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean addNfcUnlockHandler(final NfcUnlockHandler unlockHandler,
                                        String[] tagTechnologies) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -2243,7 +2413,7 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean removeNfcUnlockHandler(NfcUnlockHandler unlockHandler) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
diff --git a/core/java/android/nfc/NfcAntennaInfo.aidl b/nfc/java/android/nfc/NfcAntennaInfo.aidl
similarity index 100%
rename from core/java/android/nfc/NfcAntennaInfo.aidl
rename to nfc/java/android/nfc/NfcAntennaInfo.aidl
diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/nfc/java/android/nfc/NfcAntennaInfo.java
similarity index 100%
rename from core/java/android/nfc/NfcAntennaInfo.java
rename to nfc/java/android/nfc/NfcAntennaInfo.java
diff --git a/core/java/android/nfc/NfcControllerAlwaysOnListener.java b/nfc/java/android/nfc/NfcControllerAlwaysOnListener.java
similarity index 100%
rename from core/java/android/nfc/NfcControllerAlwaysOnListener.java
rename to nfc/java/android/nfc/NfcControllerAlwaysOnListener.java
diff --git a/core/java/android/nfc/NfcEvent.java b/nfc/java/android/nfc/NfcEvent.java
similarity index 100%
rename from core/java/android/nfc/NfcEvent.java
rename to nfc/java/android/nfc/NfcEvent.java
diff --git a/core/java/android/nfc/NfcFrameworkInitializer.java b/nfc/java/android/nfc/NfcFrameworkInitializer.java
similarity index 100%
rename from core/java/android/nfc/NfcFrameworkInitializer.java
rename to nfc/java/android/nfc/NfcFrameworkInitializer.java
diff --git a/core/java/android/nfc/NfcManager.java b/nfc/java/android/nfc/NfcManager.java
similarity index 100%
rename from core/java/android/nfc/NfcManager.java
rename to nfc/java/android/nfc/NfcManager.java
diff --git a/core/java/android/nfc/NfcServiceManager.java b/nfc/java/android/nfc/NfcServiceManager.java
similarity index 100%
rename from core/java/android/nfc/NfcServiceManager.java
rename to nfc/java/android/nfc/NfcServiceManager.java
diff --git a/core/java/android/nfc/Tag.aidl b/nfc/java/android/nfc/Tag.aidl
similarity index 100%
rename from core/java/android/nfc/Tag.aidl
rename to nfc/java/android/nfc/Tag.aidl
diff --git a/core/java/android/nfc/Tag.java b/nfc/java/android/nfc/Tag.java
similarity index 100%
rename from core/java/android/nfc/Tag.java
rename to nfc/java/android/nfc/Tag.java
diff --git a/core/java/android/nfc/TagLostException.java b/nfc/java/android/nfc/TagLostException.java
similarity index 100%
rename from core/java/android/nfc/TagLostException.java
rename to nfc/java/android/nfc/TagLostException.java
diff --git a/core/java/android/nfc/TechListParcel.aidl b/nfc/java/android/nfc/TechListParcel.aidl
similarity index 100%
rename from core/java/android/nfc/TechListParcel.aidl
rename to nfc/java/android/nfc/TechListParcel.aidl
diff --git a/core/java/android/nfc/TechListParcel.java b/nfc/java/android/nfc/TechListParcel.java
similarity index 100%
rename from core/java/android/nfc/TechListParcel.java
rename to nfc/java/android/nfc/TechListParcel.java
diff --git a/core/java/android/nfc/TransceiveResult.aidl b/nfc/java/android/nfc/TransceiveResult.aidl
similarity index 100%
rename from core/java/android/nfc/TransceiveResult.aidl
rename to nfc/java/android/nfc/TransceiveResult.aidl
diff --git a/core/java/android/nfc/TransceiveResult.java b/nfc/java/android/nfc/TransceiveResult.java
similarity index 100%
rename from core/java/android/nfc/TransceiveResult.java
rename to nfc/java/android/nfc/TransceiveResult.java
diff --git a/core/java/android/nfc/cardemulation/AidGroup.aidl b/nfc/java/android/nfc/cardemulation/AidGroup.aidl
similarity index 100%
rename from core/java/android/nfc/cardemulation/AidGroup.aidl
rename to nfc/java/android/nfc/cardemulation/AidGroup.aidl
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/nfc/java/android/nfc/cardemulation/AidGroup.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/AidGroup.java
rename to nfc/java/android/nfc/cardemulation/AidGroup.java
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.aidl b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl
similarity index 100%
rename from core/java/android/nfc/cardemulation/ApduServiceInfo.aidl
rename to nfc/java/android/nfc/cardemulation/ApduServiceInfo.aidl
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/ApduServiceInfo.java
rename to nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
similarity index 92%
rename from core/java/android/nfc/cardemulation/CardEmulation.java
rename to nfc/java/android/nfc/cardemulation/CardEmulation.java
index 58b6179..ad86d70 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -998,6 +998,87 @@
         }
     }
 
+     /**
+      * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
+      * while this Activity is in the foreground.
+      *
+      * The parameter set to null can be used to keep current values for that entry.
+      * <p>
+      * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
+      * <pre>
+      * protected void onResume() {
+      *     mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
+      * }</pre>
+      * </p>
+      * Also activities must call this method when it goes to the background,
+      * with all parameters set to null.
+      * @param activity The Activity that requests NFC controller routing table to be changed.
+      * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE".
+      * @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE".
+      * @return true if operation is successful and false otherwise
+      *
+      * This is a high risk API and only included to support mainline effort
+      * @hide
+      */
+    public boolean overrideRoutingTable(Activity activity, String protocol, String technology) {
+        if (activity == null) {
+            throw new NullPointerException("activity or service or category is null");
+        }
+        if (!activity.isResumed()) {
+            throw new IllegalArgumentException("Activity must be resumed.");
+        }
+        try {
+            return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Restore the NFC controller routing table,
+     * which was changed by {@link #overrideRoutingTable(Activity, String, String)}
+     *
+     * @param activity The Activity that requested NFC controller routing table to be changed.
+     * @return true if operation is successful and false otherwise
+     *
+     * @hide
+     */
+    public boolean recoverRoutingTable(Activity activity) {
+        if (activity == null) {
+            throw new NullPointerException("activity is null");
+        }
+        if (!activity.isResumed()) {
+            throw new IllegalArgumentException("Activity must be resumed.");
+        }
+        try {
+            return sService.recoverRoutingTable(UserHandle.myUserId());
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.recoverRoutingTable(UserHandle.myUserId());
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
     void recoverService() {
         NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
         sService = adapter.getCardEmulationService();
diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/HostApduService.java
rename to nfc/java/android/nfc/cardemulation/HostApduService.java
diff --git a/core/java/android/nfc/cardemulation/HostNfcFService.java b/nfc/java/android/nfc/cardemulation/HostNfcFService.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/HostNfcFService.java
rename to nfc/java/android/nfc/cardemulation/HostNfcFService.java
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/NfcFCardEmulation.java
rename to nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.aidl b/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
similarity index 100%
rename from core/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
rename to nfc/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/NfcFServiceInfo.java
rename to nfc/java/android/nfc/cardemulation/NfcFServiceInfo.java
diff --git a/core/java/android/nfc/cardemulation/OWNERS b/nfc/java/android/nfc/cardemulation/OWNERS
similarity index 100%
rename from core/java/android/nfc/cardemulation/OWNERS
rename to nfc/java/android/nfc/cardemulation/OWNERS
diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/nfc/java/android/nfc/cardemulation/OffHostApduService.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/OffHostApduService.java
rename to nfc/java/android/nfc/cardemulation/OffHostApduService.java
diff --git a/core/java/android/nfc/cardemulation/Utils.java b/nfc/java/android/nfc/cardemulation/Utils.java
similarity index 100%
rename from core/java/android/nfc/cardemulation/Utils.java
rename to nfc/java/android/nfc/cardemulation/Utils.java
diff --git a/core/java/android/nfc/dta/NfcDta.java b/nfc/java/android/nfc/dta/NfcDta.java
similarity index 100%
rename from core/java/android/nfc/dta/NfcDta.java
rename to nfc/java/android/nfc/dta/NfcDta.java
diff --git a/core/java/android/nfc/dta/OWNERS b/nfc/java/android/nfc/dta/OWNERS
similarity index 100%
rename from core/java/android/nfc/dta/OWNERS
rename to nfc/java/android/nfc/dta/OWNERS
diff --git a/core/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
similarity index 88%
rename from core/java/android/nfc/flags.aconfig
rename to nfc/java/android/nfc/flags.aconfig
index 0d073cc..11be905 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -55,3 +55,10 @@
     description: "Enable sending broadcasts to Wallet role holder when a tag enters/leaves the field."
     bug: "306203494"
 }
+
+flag {
+    name: "enable_nfc_set_discovery_tech"
+    namespace: "nfc"
+    description: "Flag for NFC set discovery tech API"
+    bug: "300351519"
+}
diff --git a/core/java/android/nfc/package.html b/nfc/java/android/nfc/package.html
similarity index 100%
rename from core/java/android/nfc/package.html
rename to nfc/java/android/nfc/package.html
diff --git a/core/java/android/nfc/tech/BasicTagTechnology.java b/nfc/java/android/nfc/tech/BasicTagTechnology.java
similarity index 100%
rename from core/java/android/nfc/tech/BasicTagTechnology.java
rename to nfc/java/android/nfc/tech/BasicTagTechnology.java
diff --git a/core/java/android/nfc/tech/IsoDep.java b/nfc/java/android/nfc/tech/IsoDep.java
similarity index 100%
rename from core/java/android/nfc/tech/IsoDep.java
rename to nfc/java/android/nfc/tech/IsoDep.java
diff --git a/core/java/android/nfc/tech/MifareClassic.java b/nfc/java/android/nfc/tech/MifareClassic.java
similarity index 100%
rename from core/java/android/nfc/tech/MifareClassic.java
rename to nfc/java/android/nfc/tech/MifareClassic.java
diff --git a/core/java/android/nfc/tech/MifareUltralight.java b/nfc/java/android/nfc/tech/MifareUltralight.java
similarity index 100%
rename from core/java/android/nfc/tech/MifareUltralight.java
rename to nfc/java/android/nfc/tech/MifareUltralight.java
diff --git a/core/java/android/nfc/tech/Ndef.java b/nfc/java/android/nfc/tech/Ndef.java
similarity index 100%
rename from core/java/android/nfc/tech/Ndef.java
rename to nfc/java/android/nfc/tech/Ndef.java
diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/nfc/java/android/nfc/tech/NdefFormatable.java
similarity index 100%
rename from core/java/android/nfc/tech/NdefFormatable.java
rename to nfc/java/android/nfc/tech/NdefFormatable.java
diff --git a/core/java/android/nfc/tech/NfcA.java b/nfc/java/android/nfc/tech/NfcA.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcA.java
rename to nfc/java/android/nfc/tech/NfcA.java
diff --git a/core/java/android/nfc/tech/NfcB.java b/nfc/java/android/nfc/tech/NfcB.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcB.java
rename to nfc/java/android/nfc/tech/NfcB.java
diff --git a/core/java/android/nfc/tech/NfcBarcode.java b/nfc/java/android/nfc/tech/NfcBarcode.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcBarcode.java
rename to nfc/java/android/nfc/tech/NfcBarcode.java
diff --git a/core/java/android/nfc/tech/NfcF.java b/nfc/java/android/nfc/tech/NfcF.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcF.java
rename to nfc/java/android/nfc/tech/NfcF.java
diff --git a/core/java/android/nfc/tech/NfcV.java b/nfc/java/android/nfc/tech/NfcV.java
similarity index 100%
rename from core/java/android/nfc/tech/NfcV.java
rename to nfc/java/android/nfc/tech/NfcV.java
diff --git a/core/java/android/nfc/tech/OWNERS b/nfc/java/android/nfc/tech/OWNERS
similarity index 100%
rename from core/java/android/nfc/tech/OWNERS
rename to nfc/java/android/nfc/tech/OWNERS
diff --git a/core/java/android/nfc/tech/TagTechnology.java b/nfc/java/android/nfc/tech/TagTechnology.java
similarity index 100%
rename from core/java/android/nfc/tech/TagTechnology.java
rename to nfc/java/android/nfc/tech/TagTechnology.java
diff --git a/core/java/android/nfc/tech/package.html b/nfc/java/android/nfc/tech/package.html
similarity index 100%
rename from core/java/android/nfc/tech/package.html
rename to nfc/java/android/nfc/tech/package.html
diff --git a/core/tests/nfctests/Android.bp b/nfc/tests/Android.bp
similarity index 97%
rename from core/tests/nfctests/Android.bp
rename to nfc/tests/Android.bp
index f81be49..62566ee 100644
--- a/core/tests/nfctests/Android.bp
+++ b/nfc/tests/Android.bp
@@ -30,6 +30,7 @@
         "truth",
     ],
     libs: [
+        "framework-nfc.impl",
         "android.test.runner",
     ],
     srcs: ["src/**/*.java"],
diff --git a/core/tests/nfctests/AndroidManifest.xml b/nfc/tests/AndroidManifest.xml
similarity index 100%
rename from core/tests/nfctests/AndroidManifest.xml
rename to nfc/tests/AndroidManifest.xml
diff --git a/core/tests/nfctests/AndroidTest.xml b/nfc/tests/AndroidTest.xml
similarity index 100%
rename from core/tests/nfctests/AndroidTest.xml
rename to nfc/tests/AndroidTest.xml
diff --git a/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java b/nfc/tests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
similarity index 100%
rename from core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
rename to nfc/tests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
diff --git a/core/tests/nfctests/src/android/nfc/TechListParcelTest.java b/nfc/tests/src/android/nfc/TechListParcelTest.java
similarity index 100%
rename from core/tests/nfctests/src/android/nfc/TechListParcelTest.java
rename to nfc/tests/src/android/nfc/TechListParcelTest.java
diff --git a/opengl/java/android/opengl/EGLExt.java b/opengl/java/android/opengl/EGLExt.java
index 1570e0e..31104a0 100644
--- a/opengl/java/android/opengl/EGLExt.java
+++ b/opengl/java/android/opengl/EGLExt.java
@@ -46,14 +46,6 @@
         _nativeClassInit();
     }
 
-    // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
-
-    public static native boolean eglPresentationTimeANDROID(
-        EGLDisplay dpy,
-        EGLSurface sur,
-        long time
-    );
-
     /**
      * Retrieves the SyncFence for an EGLSync created with EGL_SYNC_NATIVE_FENCE_ANDROID
      *
@@ -83,4 +75,13 @@
     }
 
     private static native int eglDupNativeFenceFDANDROIDImpl(EGLDisplay display, EGLSync sync);
+
+    // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
+
+    public static native boolean eglPresentationTimeANDROID(
+        EGLDisplay dpy,
+        EGLSurface sur,
+        long time
+    );
+
 }
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index f6458c2..ce32ec4 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -46,4 +46,6 @@
     ],
 
     platform_apis: true,
+
+    generate_product_characteristics_rro: true,
 }
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
new file mode 100644
index 0000000..5636266
--- /dev/null
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.crashrecovery.flags"
+
+flag {
+    name: "recoverability_detection"
+    namespace: "package_watchdog"
+    description: "Feature flag for recoverability detection"
+    bug: "310236690"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/packages/CrashRecovery/framework/Android.bp b/packages/CrashRecovery/framework/Android.bp
index b2af315..c0d93531 100644
--- a/packages/CrashRecovery/framework/Android.bp
+++ b/packages/CrashRecovery/framework/Android.bp
@@ -1,9 +1,55 @@
-filegroup {
+soong_config_module_type {
+    name: "platform_filegroup",
+    module_type: "filegroup",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "move_crashrecovery_files",
+    ],
+    properties: [
+        "srcs",
+    ],
+}
+
+platform_filegroup {
     name: "framework-crashrecovery-sources",
     srcs: [
         "java/**/*.java",
         "java/**/*.aidl",
     ],
+    soong_config_variables: {
+        // if the flag is enabled, then files would be moved to module
+        move_crashrecovery_files: {
+            srcs: [],
+        },
+    },
     path: "java",
     visibility: ["//frameworks/base:__subpackages__"],
 }
+
+soong_config_module_type {
+    name: "module_filegroup",
+    module_type: "filegroup",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "move_crashrecovery_files",
+    ],
+    properties: [
+        "srcs",
+    ],
+}
+
+module_filegroup {
+    name: "framework-crashrecovery-module-sources",
+    srcs: [],
+    soong_config_variables: {
+        // if the flag is enabled, then files would be moved to module
+        move_crashrecovery_files: {
+            srcs: [
+                "java/**/*.java",
+                "java/**/*.aidl",
+            ],
+        },
+    },
+    path: "java",
+    visibility: ["//packages/modules/CrashRecovery/framework"],
+}
diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp
index 27ddff9..ab10b5a 100644
--- a/packages/CrashRecovery/services/Android.bp
+++ b/packages/CrashRecovery/services/Android.bp
@@ -1,9 +1,63 @@
-filegroup {
+soong_config_module_type {
+    name: "platform_filegroup",
+    module_type: "filegroup",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "move_crashrecovery_files",
+    ],
+    properties: [
+        "srcs",
+    ],
+}
+
+platform_filegroup {
     name: "services-crashrecovery-sources",
     srcs: [
         "java/**/*.java",
         "java/**/*.aidl",
+        ":statslog-crashrecovery-java-gen",
     ],
-    path: "java",
+    soong_config_variables: {
+        // if the flag is enabled, then files would be moved to module
+        move_crashrecovery_files: {
+            srcs: [],
+        },
+    },
     visibility: ["//frameworks/base:__subpackages__"],
 }
+
+soong_config_module_type {
+    name: "module_filegroup",
+    module_type: "filegroup",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "move_crashrecovery_files",
+    ],
+    properties: [
+        "srcs",
+    ],
+}
+
+module_filegroup {
+    name: "services-crashrecovery-module-sources",
+    srcs: [],
+    soong_config_variables: {
+        // if the flag is enabled, then files would be moved to module
+        move_crashrecovery_files: {
+            srcs: [
+                "java/**/*.java",
+                "java/**/*.aidl",
+                ":statslog-crashrecovery-java-gen",
+            ],
+        },
+    },
+    visibility: ["//packages/modules/CrashRecovery/service"],
+}
+
+genrule {
+    name: "statslog-crashrecovery-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module crashrecovery " +
+        "--javaPackage com.android.server.crashrecovery.proto --javaClass CrashRecoveryStatsLog --worksource",
+    out: ["com/android/server/crashrecovery/proto/CrashRecoveryStatsLog.java"],
+}
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index eb65b2a..5d03ef5 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -46,11 +46,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 import com.android.server.am.SettingsToPropertiesMapper;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -382,7 +382,16 @@
 
     private static void executeRescueLevelInternal(Context context, int level, @Nullable
             String failedPackage) throws Exception {
-        FrameworkStatsLog.write(FrameworkStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
+
+        if (level <= LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS) {
+            // Disabling flag resets on master branch for trunk stable launch.
+            // TODO(b/287618292): Re-enable them after the trunk stable is launched and we
+            // figured out a way to reset flags without interfering with trunk development.
+            return;
+        }
+
+        CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
+                level, levelToString(level));
         // Try our best to reset all settings possible, and once finished
         // rethrow any exception that we encountered
         Exception res = null;
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 2007079..50322f0 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -39,13 +39,13 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.Preconditions;
 import com.android.server.PackageWatchdog;
 import com.android.server.PackageWatchdog.FailureReasons;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 import com.android.server.SystemConfig;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
 import com.android.server.pm.ApexManager;
 
 import java.io.BufferedReader;
@@ -418,7 +418,7 @@
 
         final VersionedPackage logPackage = logPackageTemp;
         WatchdogRollbackLogger.logEvent(logPackage,
-                FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
+                CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
                 reasonToLog, failedPackageToLog);
 
         Consumer<Intent> onResult = result -> {
@@ -430,19 +430,19 @@
                     int rollbackId = rollback.getRollbackId();
                     saveStagedRollbackId(rollbackId, logPackage);
                     WatchdogRollbackLogger.logEvent(logPackage,
-                            FrameworkStatsLog
+                            CrashRecoveryStatsLog
                             .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
                             reasonToLog, failedPackageToLog);
 
                 } else {
                     WatchdogRollbackLogger.logEvent(logPackage,
-                            FrameworkStatsLog
+                            CrashRecoveryStatsLog
                                     .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
                             reasonToLog, failedPackageToLog);
                 }
             } else {
                 WatchdogRollbackLogger.logEvent(logPackage,
-                        FrameworkStatsLog
+                        CrashRecoveryStatsLog
                                 .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
                         reasonToLog, failedPackageToLog);
             }
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
index f9ef994..898c543 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -16,16 +16,16 @@
 
 package com.android.server.rollback;
 
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
-import static com.android.internal.util.FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -42,8 +42,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.PackageWatchdog;
+import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
 
 import java.util.List;
 import java.util.Set;
@@ -197,8 +197,8 @@
                 + " rollbackReason: " + rollbackReasonToString(rollbackReason)
                 + " failedPackageName: " + failingPackageName);
         if (logPackage != null) {
-            FrameworkStatsLog.write(
-                    FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+            CrashRecoveryStatsLog.write(
+                    CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
                     type,
                     logPackage.getPackageName(),
                     logPackage.getVersionCode(),
@@ -208,8 +208,8 @@
         } else {
             // In the case that the log package is null, still log an empty string as an
             // indication that retrieving the logging parent failed.
-            FrameworkStatsLog.write(
-                    FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
+            CrashRecoveryStatsLog.write(
+                    CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
                     type,
                     "",
                     0,
diff --git a/packages/CtsShim/OWNERS b/packages/CtsShim/OWNERS
index 9419771..f5741a0 100644
--- a/packages/CtsShim/OWNERS
+++ b/packages/CtsShim/OWNERS
@@ -1,3 +1,2 @@
-ioffe@google.com
-toddke@google.com
-patb@google.com
\ No newline at end of file
+include /PACKAGE_MANAGER_OWNERS
+ioffe@google.com
\ No newline at end of file
diff --git a/packages/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS
index 2736870..acbdff8 100644
--- a/packages/PackageInstaller/OWNERS
+++ b/packages/PackageInstaller/OWNERS
@@ -1,5 +1,4 @@
-svetoslavganov@google.com
+# Bug component: 1002434
 include /PACKAGE_MANAGER_OWNERS
 
-# For automotive related changes
-rogerxue@google.com
+sumedhsen@google.com
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
index 84fea15..d92a863 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/MoreOptions.kt
@@ -35,8 +35,8 @@
 /**
  * Scope for the children of [MoreOptionsAction].
  */
-interface MoreOptionsScope {
-    fun dismiss()
+abstract class MoreOptionsScope {
+    abstract fun dismiss()
 
     @Composable
     fun MenuItem(text: String, enabled: Boolean = true, onClick: () -> Unit) {
@@ -60,7 +60,7 @@
     val onDismiss = { expanded = false }
     DropdownMenu(expanded = expanded, onDismissRequest = onDismiss) {
         val moreOptionsScope = remember(this) {
-            object : MoreOptionsScope {
+            object : MoreOptionsScope() {
                 override fun dismiss() {
                     onDismiss()
                 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 1a7d896..1859c9b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -37,6 +37,7 @@
 interface AppRepository {
     fun loadLabel(app: ApplicationInfo): String
 
+    @Suppress("ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE")
     @Composable
     fun produceLabel(app: ApplicationInfo, isClonedAppPage: Boolean = false): State<String> {
         val context = LocalContext.current
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
index 983284c..2ccf323 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
@@ -130,7 +130,7 @@
     }
 
     private fun setContent(restrictions: Restrictions) {
-        val fakeMoreOptionsScope = object : MoreOptionsScope {
+        val fakeMoreOptionsScope = object : MoreOptionsScope() {
             override fun dismiss() {}
         }
         composeTestRule.setContent {
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
index 1d433e7..943e3fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
@@ -59,7 +59,7 @@
 
     @Override
     public boolean isAvailable() {
-        return true;
+        return mWifiManager != null;
     }
 
     @Override
@@ -70,10 +70,8 @@
     @Override
     public void displayPreference(PreferenceScreen screen) {
         super.displayPreference(screen);
-        if (isAvailable()) {
-            mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
-            updateConnectivity();
-        }
+        mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
+        updateConnectivity();
     }
 
     @Override
@@ -84,16 +82,16 @@
     @SuppressLint("HardwareIds")
     @Override
     protected void updateConnectivity() {
+        if (mWifiManager == null || mWifiMacAddress == null) {
+            return;
+        }
+
         final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
         String macAddress = null;
         if (macAddresses != null && macAddresses.length > 0) {
             macAddress = macAddresses[0];
         }
 
-        if (mWifiMacAddress == null) {
-            return;
-        }
-
         if (TextUtils.isEmpty(macAddress) || macAddress.equals(WifiInfo.DEFAULT_MAC_ADDRESS)) {
             mWifiMacAddress.setSummary(R.string.status_unavailable);
         } else {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 91c72b5..bc93c5b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -280,6 +280,7 @@
                     Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
                     Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
                     Settings.Global.ENHANCED_4G_MODE_ENABLED,
+                    Settings.Global.ENABLE_16K_PAGES, // Added for 16K developer option
                     Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
                     Settings.Global.ERROR_LOGCAT_PREFIX,
                     Settings.Global.EUICC_PROVISIONED,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ad08c51..99383d9 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
 }
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index f358417..1568d59 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
index 17db1ac..06ace37 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 6f53b42..d700f87 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index d3f66e3..4cbc18c 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp
index e36ada8..53bdf38 100644
--- a/packages/SystemUI/common/Android.bp
+++ b/packages/SystemUI/common/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index ab3efb8..ce9445e 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/core/tests/Android.bp b/packages/SystemUI/compose/core/tests/Android.bp
index c008bb1..ce2ba03 100644
--- a/packages/SystemUI/compose/core/tests/Android.bp
+++ b/packages/SystemUI/compose/core/tests/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index c6438c9..0fc91c8 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/compose/features/tests/Android.bp b/packages/SystemUI/compose/features/tests/Android.bp
index c7c9140..77a60b8 100644
--- a/packages/SystemUI/compose/features/tests/Android.bp
+++ b/packages/SystemUI/compose/features/tests/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index fc37b355..7fbcb0d 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/log/Android.bp b/packages/SystemUI/log/Android.bp
index 627ac4b..36d5c2f 100644
--- a/packages/SystemUI/log/Android.bp
+++ b/packages/SystemUI/log/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/monet/Android.bp b/packages/SystemUI/monet/Android.bp
index 507ea25..98f7ace 100644
--- a/packages/SystemUI/monet/Android.bp
+++ b/packages/SystemUI/monet/Android.bp
@@ -14,6 +14,7 @@
 // limitations under the License.
 //
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index bb47a2f..cb8ec9a 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/plugin/ExamplePlugin/Android.bp b/packages/SystemUI/plugin/ExamplePlugin/Android.bp
index 3f0fded..cbfa3f8 100644
--- a/packages/SystemUI/plugin/ExamplePlugin/Android.bp
+++ b/packages/SystemUI/plugin/ExamplePlugin/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/plugin_core/Android.bp b/packages/SystemUI/plugin_core/Android.bp
index 4e39f1a..e8e54c3 100644
--- a/packages/SystemUI/plugin_core/Android.bp
+++ b/packages/SystemUI/plugin_core/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml b/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml
new file mode 100644
index 0000000..2161a62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_no_internet_branded_vpn.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="17dp"
+    android:height="17dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M12.09,9C11.11,7.5 9.43,6.5 7.5,6.5C4.46,6.5 2,8.96 2,12c0,3.04 2.46,5.5 5.5,5.5c1.93,0 3.61,-1 4.59,-2.5H14v3h4V9H12.09zM18,13hv3h-2v-3h-5.16c-0.43,1.44 -1.76,2.5 -3.34,2.5C5.57,15.5 4,13.93 4,12c0,-1.93 1.57,-3.5 3.5,-3.5c1.58,0 2.9,1.06 3.34,2.5H18V13z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M7.5,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M22,10h-2v8h2V10z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml b/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml
new file mode 100644
index 0000000..2161a62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_no_internet_vpn_ic.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="17dp"
+    android:height="17dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M12.09,9C11.11,7.5 9.43,6.5 7.5,6.5C4.46,6.5 2,8.96 2,12c0,3.04 2.46,5.5 5.5,5.5c1.93,0 3.61,-1 4.59,-2.5H14v3h4V9H12.09zM18,13hv3h-2v-3h-5.16c-0.43,1.44 -1.76,2.5 -3.34,2.5C5.57,15.5 4,13.93 4,12c0,-1.93 1.57,-3.5 3.5,-3.5c1.58,0 2.9,1.06 3.34,2.5H18V13z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M7.5,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M22,10h-2v8h2V10z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M22,20h-2v2h2V20z"/>
+</vector>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index e2d27f1..ffa398b 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index c6d1471..edf9517 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -33,7 +33,7 @@
 public abstract class KeyguardAbsKeyInputView extends KeyguardInputView {
     protected View mEcaView;
 
-    // To avoid accidental lockout due to events while the device in in the pocket, ignore
+    // To avoid accidental lockout due to events while the device in the pocket, ignore
     // any passwords with length less than or equal to this length.
     protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
     private KeyDownListener mKeyDownListener;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index bc12aee..ce03072 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -132,7 +132,7 @@
     boolean shouldSubtleWindowAnimationsForUnlock();
 
     /**
-     * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
+     * Starts the animation before we dismiss Keyguard, i.e. a disappearing animation on the
      * security view of the bouncer.
      *
      * @param finishRunnable the runnable to be run after the animation finished, or {@code null} if
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index 9b83b75..ee3706a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
@@ -80,7 +80,7 @@
     }
 
     /**
-     * Click listener for messsage.
+     * Click listener for message.
      */
     public @Nullable View.OnClickListener getClickListener() {
         return mOnClickListener;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 1ae5d0a..c385011 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -437,7 +437,7 @@
     /**
      * Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be
      * called.
-     * */
+     */
     private boolean mHiding;
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 344e56c..ae7c170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -118,15 +118,25 @@
 
     private void updateVpn() {
         boolean vpnVisible = mSecurityController.isVpnEnabled();
-        int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
+        int vpnIconId = currentVpnIconId(
+                mSecurityController.isVpnBranded(),
+                mSecurityController.isVpnValidated());
 
         mIconController.setIcon(mSlotVpn, vpnIconId,
                 mContext.getResources().getString(R.string.accessibility_vpn_on));
         mIconController.setIconVisibility(mSlotVpn, vpnVisible);
     }
 
-    private int currentVpnIconId(boolean isBranded) {
-        return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
+    private int currentVpnIconId(boolean isBranded, boolean isValidated) {
+        if (isBranded) {
+            return isValidated
+                    ? R.drawable.stat_sys_branded_vpn
+                    : R.drawable.stat_sys_no_internet_branded_vpn;
+        } else {
+            return isValidated
+                    ? R.drawable.stat_sys_vpn_ic
+                    : R.drawable.stat_sys_no_internet_vpn_ic;
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 3be14bc..10bf068 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -48,6 +48,8 @@
     boolean isNetworkLoggingEnabled();
     boolean isVpnEnabled();
     boolean isVpnRestricted();
+    /** Whether the VPN network is validated. */
+    boolean isVpnValidated();
     /** Whether the VPN app should use branded VPN iconography.  */
     boolean isVpnBranded();
     String getPrimaryVpnName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 03656f0..ada2196 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -15,6 +15,9 @@
  */
 package com.android.systemui.statusbar.policy;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+
 import android.annotation.Nullable;
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManager;
@@ -32,7 +35,9 @@
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.VpnManager;
 import android.os.Handler;
@@ -76,7 +81,10 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final NetworkRequest REQUEST =
-            new NetworkRequest.Builder().clearCapabilities().build();
+            new NetworkRequest.Builder()
+                    .clearCapabilities()
+                    .addTransportType(TRANSPORT_VPN)
+                    .build();
     private static final int NO_NETWORK = -1;
 
     private static final String VPN_BRANDED_META_DATA = "com.android.systemui.IS_BRANDED";
@@ -99,6 +107,8 @@
     private SparseArray<VpnConfig> mCurrentVpns = new SparseArray<>();
     private int mCurrentUserId;
     private int mVpnUserId;
+    @GuardedBy("mNetworkProperties")
+    private final SparseArray<NetworkProperties> mNetworkProperties = new SparseArray<>();
 
     // Key: userId, Value: whether the user has CACerts installed
     // Needs to be cached here since the query has to be asynchronous
@@ -162,6 +172,21 @@
             pw.print(mCurrentVpns.valueAt(i).user);
         }
         pw.println("}");
+        pw.print("  mNetworkProperties={");
+        synchronized (mNetworkProperties) {
+            for (int i = 0; i < mNetworkProperties.size(); ++i) {
+                if (i > 0) {
+                    pw.print(", ");
+                }
+                pw.print(mNetworkProperties.keyAt(i));
+                pw.print("={");
+                pw.print(mNetworkProperties.valueAt(i).interfaceName);
+                pw.print(", ");
+                pw.print(mNetworkProperties.valueAt(i).validated);
+                pw.print("}");
+            }
+        }
+        pw.println("}");
     }
 
     @Override
@@ -304,6 +329,26 @@
     }
 
     @Override
+    public boolean isVpnValidated() {
+        // Prioritize reporting the network status of the parent user.
+        final VpnConfig primaryVpnConfig = mCurrentVpns.get(mVpnUserId);
+        if (primaryVpnConfig != null) {
+            return getVpnValidationStatus(primaryVpnConfig);
+        }
+        // Identify any Unvalidated status in each active VPN network within other profiles.
+        for (int profileId : mUserManager.getEnabledProfileIds(mVpnUserId)) {
+            final VpnConfig vpnConfig = mCurrentVpns.get(profileId);
+            if (vpnConfig == null) {
+                continue;
+            }
+            if (!getVpnValidationStatus(vpnConfig)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
     public boolean hasCACertInCurrentUser() {
         Boolean hasCACerts = mHasCACerts.get(mCurrentUserId);
         return hasCACerts != null && hasCACerts.booleanValue();
@@ -491,11 +536,74 @@
         @Override
         public void onLost(Network network) {
             if (DEBUG) Log.d(TAG, "onLost " + network.getNetId());
+            synchronized (mNetworkProperties) {
+                mNetworkProperties.delete(network.getNetId());
+            }
             updateState();
             fireCallbacks();
         };
+
+
+        @Override
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+            if (DEBUG) Log.d(TAG, "onCapabilitiesChanged " + network.getNetId());
+            final NetworkProperties properties;
+            synchronized (mNetworkProperties) {
+                properties = mNetworkProperties.get(network.getNetId());
+            }
+            // When a new network appears, the system first notifies the application about
+            // its capabilities through onCapabilitiesChanged. This initial notification
+            // will be skipped because the interface information is included in the
+            // subsequent onLinkPropertiesChanged call. After validating the network, the
+            // system might send another onCapabilitiesChanged notification if the network
+            // becomes validated.
+            if (properties == null) {
+                return;
+            }
+            final boolean validated = nc.hasCapability(NET_CAPABILITY_VALIDATED);
+            if (properties.validated != validated) {
+                properties.validated = validated;
+                fireCallbacks();
+            }
+        }
+
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
+            if (DEBUG) Log.d(TAG, "onLinkPropertiesChanged " + network.getNetId());
+            final String interfaceName = linkProperties.getInterfaceName();
+            if (interfaceName == null) {
+                Log.w(TAG, "onLinkPropertiesChanged event with null interface");
+                return;
+            }
+            synchronized (mNetworkProperties) {
+                final NetworkProperties properties = mNetworkProperties.get(network.getNetId());
+                if (properties == null) {
+                    mNetworkProperties.put(
+                            network.getNetId(),
+                            new NetworkProperties(interfaceName, false));
+                } else {
+                    properties.interfaceName = interfaceName;
+                }
+            }
+        }
     };
 
+    /**
+     *  Retrieve the validation status of the VPN network associated with the given VpnConfig.
+     */
+    private boolean getVpnValidationStatus(@NonNull VpnConfig vpnConfig) {
+        synchronized (mNetworkProperties) {
+            // Find the network has the same interface as the VpnConfig
+            for (int i = 0; i < mNetworkProperties.size(); ++i) {
+                if (mNetworkProperties.valueAt(i).interfaceName.equals(vpnConfig.interfaze)) {
+                    return mNetworkProperties.valueAt(i).validated;
+                }
+            }
+        }
+        // If no matching network is found, consider it validated.
+        return true;
+    }
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
             if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
@@ -506,4 +614,17 @@
             }
         }
     };
+
+    /**
+     *  A data class to hold specific Network properties received through the NetworkCallback.
+     */
+    private static class NetworkProperties {
+        public String interfaceName;
+        public boolean validated;
+
+        NetworkProperties(@NonNull String interfaceName, boolean validated) {
+            this.interfaceName = interfaceName;
+            this.validated = validated;
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/Android.bp b/packages/SystemUI/tests/Android.bp
index 3c418ed..1594ec5 100644
--- a/packages/SystemUI/tests/Android.bp
+++ b/packages/SystemUI/tests/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index c35bc69..bcc9272 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.policy;
 
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -213,7 +214,8 @@
     public void testNetworkRequest() {
         verify(mConnectivityManager, times(1)).registerNetworkCallback(argThat(
                 (NetworkRequest request) ->
-                        request.equals(new NetworkRequest.Builder().clearCapabilities().build())
+                        request.equals(new NetworkRequest.Builder()
+                                .clearCapabilities().addTransportType(TRANSPORT_VPN).build())
                 ), any(NetworkCallback.class));
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
index 021e7df..ac90a45 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
@@ -77,6 +77,8 @@
 
     override fun isVpnBranded(): Boolean = fakeState.isVpnBranded
 
+    override fun isVpnValidated(): Boolean = fakeState.isVpnValidated
+
     override fun getPrimaryVpnName(): String? = fakeState.primaryVpnName
 
     override fun getWorkProfileVpnName(): String? = fakeState.workProfileVpnName
@@ -110,6 +112,7 @@
         var isVpnEnabled: Boolean = false,
         var isVpnRestricted: Boolean = false,
         var isVpnBranded: Boolean = false,
+        var isVpnValidated: Boolean = false,
         var primaryVpnName: String? = null,
         var workProfileVpnName: String? = null,
         var hasCACertInCurrentUser: Boolean = false,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
index 76199e3..791165d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -109,6 +109,11 @@
     }
 
     @Override
+    public boolean isVpnValidated() {
+        return false;
+    }
+
+    @Override
     public String getPrimaryVpnName() {
         return null;
     }
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index a1617a6..c8109e6 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index da9235f..5b06894 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -16,7 +16,6 @@
 
 package com.android.server.autofill;
 
-import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
 import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
 import static android.service.autofill.Dataset.PICK_REASON_NO_PCC;
@@ -109,8 +108,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
@@ -1528,6 +1525,17 @@
                 return;
             }
 
+            if (mSessionFlags.mShowingSaveUi) {
+                // Even though the session has not yet been destroyed at this point, after the
+                // saveUi gets closed, the session will be destroyed and AutofillManager will reset
+                // its state. Processing the fill request will result in a great chance of corrupt
+                // state in Autofill.
+                Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
+                        + id + " is showing saveUi");
+                mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
+                mFillResponseEventLogger.logAndEndEvent();
+                return;
+            }
 
             requestLog = mRequestLogs.get(requestId);
             if (requestLog != null) {
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 720687e..0e66fbc 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -23,12 +23,12 @@
 import android.os.Build;
 import android.util.Slog;
 
-import com.google.security.cryptauth.lib.securegcm.BadHandleException;
-import com.google.security.cryptauth.lib.securegcm.CryptoException;
-import com.google.security.cryptauth.lib.securegcm.D2DConnectionContextV1;
-import com.google.security.cryptauth.lib.securegcm.D2DHandshakeContext;
-import com.google.security.cryptauth.lib.securegcm.D2DHandshakeContext.Role;
-import com.google.security.cryptauth.lib.securegcm.HandshakeException;
+import com.google.security.cryptauth.lib.securegcm.ukey2.BadHandleException;
+import com.google.security.cryptauth.lib.securegcm.ukey2.CryptoException;
+import com.google.security.cryptauth.lib.securegcm.ukey2.D2DConnectionContextV1;
+import com.google.security.cryptauth.lib.securegcm.ukey2.D2DHandshakeContext;
+import com.google.security.cryptauth.lib.securegcm.ukey2.D2DHandshakeContext.Role;
+import com.google.security.cryptauth.lib.securegcm.ukey2.HandshakeException;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
diff --git a/services/contextualsearch/OWNERS b/services/contextualsearch/OWNERS
new file mode 100644
index 0000000..0c2612c
--- /dev/null
+++ b/services/contextualsearch/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/contextualsearch/OWNERS
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 21cfd242..2dda76e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -190,6 +190,7 @@
         "com.android.sysprop.watchdog",
         "ImmutabilityAnnotation",
         "securebox",
+        "net_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index a2d7a81..5335cc3 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -43,3 +43,6 @@
 per-file TelephonyRegistry.java = file:/telephony/OWNERS
 per-file UiModeManagerService.java = file:/packages/SystemUI/OWNERS
 per-file VcnManagementService.java = file:/services/core/java/com/android/server/vcn/OWNERS
+
+# SystemConfig
+per-file SystemConfig.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 1595a35..f952c99 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1341,8 +1341,8 @@
 
         final int flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
         for (UserInfo user : users) {
-            prepareUserStorageInternal(fromVolumeUuid, user.id, user.serialNumber, flags);
-            prepareUserStorageInternal(toVolumeUuid, user.id, user.serialNumber, flags);
+            prepareUserStorageInternal(fromVolumeUuid, user.id, flags);
+            prepareUserStorageInternal(toVolumeUuid, user.id, flags);
         }
     }
 
@@ -3210,12 +3210,12 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) {
+    public void createUserStorageKeys(int userId, boolean ephemeral) {
 
         super.createUserStorageKeys_enforcePermission();
 
         try {
-            mVold.createUserStorageKeys(userId, serialNumber, ephemeral);
+            mVold.createUserStorageKeys(userId, ephemeral);
             // Since the user's CE key was just created, the user's CE storage is now unlocked.
             synchronized (mLock) {
                 mCeUnlockedUsers.append(userId);
@@ -3255,12 +3255,11 @@
     /* Only for use by LockSettingsService */
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void unlockCeStorage(@UserIdInt int userId, int serialNumber, byte[] secret)
-            throws RemoteException {
+    public void unlockCeStorage(@UserIdInt int userId, byte[] secret) throws RemoteException {
         super.unlockCeStorage_enforcePermission();
 
         if (StorageManager.isFileEncrypted()) {
-            mVold.unlockCeStorage(userId, serialNumber, HexDump.toHexString(secret));
+            mVold.unlockCeStorage(userId, HexDump.toHexString(secret));
         }
         synchronized (mLock) {
             mCeUnlockedUsers.append(userId);
@@ -3327,27 +3326,27 @@
                 continue;
             }
 
-            prepareUserStorageInternal(vol.fsUuid, user.id, user.serialNumber, flags);
+            prepareUserStorageInternal(vol.fsUuid, user.id, flags);
         }
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
+    public void prepareUserStorage(String volumeUuid, int userId, int flags) {
 
         super.prepareUserStorage_enforcePermission();
 
         try {
-            prepareUserStorageInternal(volumeUuid, userId, serialNumber, flags);
+            prepareUserStorageInternal(volumeUuid, userId, flags);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
-    private void prepareUserStorageInternal(String volumeUuid, int userId, int serialNumber,
-            int flags) throws Exception {
+    private void prepareUserStorageInternal(String volumeUuid, int userId, int flags)
+            throws Exception {
         try {
-            mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+            mVold.prepareUserStorage(volumeUuid, userId, flags);
             // After preparing user storage, we should check if we should mount data mirror again,
             // and we do it for user 0 only as we only need to do once for all users.
             if (volumeUuid != null) {
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index bca2d60..b04c7c5 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -316,6 +316,11 @@
     private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
     private final ArraySet<String> mAppDataIsolationWhitelistedApps = new ArraySet<>();
 
+    // These packages will be set as 'prevent disable', where they are no longer possible
+    // for the end user to disable via settings. This flag should only be used for packages
+    // which meet the 'force or keep enabled apps' policy.
+    private final ArrayList<String> mPreventUserDisablePackages = new ArrayList<>();
+
     // Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService().
     private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
     private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
@@ -501,6 +506,10 @@
         return mAppDataIsolationWhitelistedApps;
     }
 
+    public @NonNull ArrayList<String> getPreventUserDisablePackages() {
+        return mPreventUserDisablePackages;
+    }
+
     /**
      * Gets map of packagesNames to userTypes, dictating on which user types each package should be
      * initially installed, and then removes this map from SystemConfig.
@@ -1303,6 +1312,16 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "prevent-disable": {
+                        String pkgname = parser.getAttributeValue(null, "package");
+                        if (pkgname == null) {
+                            Slog.w(TAG, "<" + name + "> without package in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else {
+                            mPreventUserDisablePackages.add(pkgname);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "install-in-user-type": {
                         // NB: We allow any directory permission to declare install-in-user-type.
                         readInstallInUserType(parser,
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index f95d6ea..a564b7d 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -164,6 +164,7 @@
     public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] {
             "android.hardware.audio.core.IModule/",
             "android.hardware.audio.core.IConfig/",
+            "android.hardware.audio.effect.IFactory/",
             "android.hardware.biometrics.face.IFace/",
             "android.hardware.biometrics.fingerprint.IFingerprint/",
             "android.hardware.bluetooth.IBluetoothHci/",
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 2aed847..0f75ad48 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -31,7 +31,7 @@
 30017 am_low_memory (Num Processes|1|1)
 
 # Kill a process to reclaim memory.
-30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3)
+30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3),(Rss|2|2)
 # Discard an undelivered serialized broadcast (timeout/ANR/crash)
 30024 am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5)
 30025 am_broadcast_discard_app (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3)
diff --git a/services/core/java/com/android/server/am/PhantomProcessRecord.java b/services/core/java/com/android/server/am/PhantomProcessRecord.java
index 1a692df..ac96bdc 100644
--- a/services/core/java/com/android/server/am/PhantomProcessRecord.java
+++ b/services/core/java/com/android/server/am/PhantomProcessRecord.java
@@ -105,6 +105,11 @@
         }
     }
 
+    public long getRss(int pid) {
+        long[] rss = Process.getRss(pid);
+        return (rss != null && rss.length > 0) ? rss[0] : 0;
+    }
+
     @GuardedBy("mLock")
     void killLocked(String reason, boolean noisy) {
         if (!mKilled) {
@@ -115,7 +120,7 @@
             }
             if (mPid > 0) {
                 EventLog.writeEvent(EventLogTags.AM_KILL, UserHandle.getUserId(mUid),
-                        mPid, mProcessName, mAdj, reason);
+                        mPid, mProcessName, mAdj, reason, getRss(mPid));
                 if (!Process.supportsPidFd()) {
                     onProcDied(false);
                 } else {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 43e9b43..1ad85426 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2879,7 +2879,11 @@
         return true;
     }
 
-    private static void freezeBinderAndPackageCgroup(ArrayList<Pair<ProcessRecord, Boolean>> procs,
+    private static boolean unfreezePackageCgroup(int packageUID) {
+        return freezePackageCgroup(packageUID, false);
+    }
+
+    private static void freezeBinderAndPackageCgroup(List<Pair<ProcessRecord, Boolean>> procs,
                                                      int packageUID) {
         // Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
         // Since we're going to kill these, we don't need to unfreze them later.
@@ -2887,12 +2891,9 @@
         // processes (forks) should not be Binder users.
         int N = procs.size();
         for (int i = 0; i < N; i++) {
-            final int uid = procs.get(i).first.uid;
             final int pid = procs.get(i).first.getPid();
             int nRetries = 0;
-            // We only freeze the cgroup of the target package, so we do not need to freeze the
-            // Binder interfaces of dependant processes in other UIDs.
-            if (pid > 0 && uid == packageUID) {
+            if (pid > 0) {
                 try {
                     int rc;
                     do {
@@ -2906,12 +2907,19 @@
         }
 
         // We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze
-        // despite being added to a new child cgroup. The cgroups of package dependant processes are
-        // not frozen, since it's possible this would freeze processes with no dependency on the
-        // package being killed here.
+        // despite being added to a child cgroup created after this call that would otherwise be
+        // unfrozen.
         freezePackageCgroup(packageUID, true);
     }
 
+    private static List<Pair<ProcessRecord, Boolean>> getUIDSublist(
+            List<Pair<ProcessRecord, Boolean>> procs, int startIdx) {
+        final int uid = procs.get(startIdx).first.uid;
+        int endIdx = startIdx + 1;
+        while (endIdx < procs.size() && procs.get(endIdx).first.uid == uid) ++endIdx;
+        return procs.subList(startIdx, endIdx);
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     boolean killPackageProcessesLSP(String packageName, int appId,
             int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -3007,25 +3015,36 @@
             }
         }
 
-        final int packageUID = UserHandle.getUid(userId, appId);
-        final boolean doFreeze = appId >= Process.FIRST_APPLICATION_UID
-                              && appId <= Process.LAST_APPLICATION_UID;
-        if (doFreeze) {
-            freezeBinderAndPackageCgroup(procs, packageUID);
+        final boolean killingUserApp = appId >= Process.FIRST_APPLICATION_UID
+                                    && appId <= Process.LAST_APPLICATION_UID;
+
+        if (killingUserApp) {
+            procs.sort((o1, o2) -> Integer.compare(o1.first.uid, o2.first.uid));
         }
 
-        int N = procs.size();
-        for (int i=0; i<N; i++) {
-            final Pair<ProcessRecord, Boolean> proc = procs.get(i);
-            removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
-                    reasonCode, subReason, reason, !doFreeze /* async */);
+        int idx = 0;
+        while (idx < procs.size()) {
+            final List<Pair<ProcessRecord, Boolean>> uidProcs = getUIDSublist(procs, idx);
+            final int packageUID = uidProcs.get(0).first.uid;
+
+            // Do not freeze for system apps or for dependencies of the targeted package, but
+            // make sure to freeze the targeted package for all users if called with USER_ALL.
+            final boolean doFreeze = killingUserApp && UserHandle.getAppId(packageUID) == appId;
+
+            if (doFreeze) freezeBinderAndPackageCgroup(uidProcs, packageUID);
+
+            for (Pair<ProcessRecord, Boolean> proc : uidProcs) {
+                removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
+                        reasonCode, subReason, reason, !doFreeze /* async */);
+            }
+            killAppZygotesLocked(packageName, appId, userId, false /* force */);
+
+            if (doFreeze) unfreezePackageCgroup(packageUID);
+
+            idx += uidProcs.size();
         }
-        killAppZygotesLocked(packageName, appId, userId, false /* force */);
         mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
-        if (doFreeze) {
-            freezePackageCgroup(packageUID, false);
-        }
-        return N > 0;
+        return procs.size() > 0;
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 908827b..113e2fa 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1170,6 +1170,11 @@
         }
     }
 
+    public long getRss(int pid) {
+        long[] rss = Process.getRss(pid);
+        return (rss != null && rss.length > 0) ? rss[0] : 0;
+    }
+
     @GuardedBy("mService")
     void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
         killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy, true);
@@ -1213,7 +1218,7 @@
             if (mPid > 0) {
                 mService.mProcessList.noteAppKill(this, reasonCode, subReason, description);
                 EventLog.writeEvent(EventLogTags.AM_KILL,
-                        userId, mPid, processName, mState.getSetAdj(), reason);
+                        userId, mPid, processName, mState.getSetAdj(), reason, getRss(mPid));
                 Process.killProcessQuiet(mPid);
                 killProcessGroupIfNecessaryLocked(asyncKPG);
             } else {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index b9535d6..43ecec4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1024,11 +1024,9 @@
     private void initAudioHalBluetoothState() {
         synchronized (mBluetoothAudioStateLock) {
             mBluetoothScoOnApplied = false;
-            AudioSystem.setParameters("BT_SCO=off");
             mBluetoothA2dpSuspendedApplied = false;
-            AudioSystem.setParameters("A2dpSuspended=false");
             mBluetoothLeSuspendedApplied = false;
-            AudioSystem.setParameters("LeAudioSuspended=false");
+            reapplyAudioHalBluetoothState();
         }
     }
 
@@ -1091,6 +1089,34 @@
         }
     }
 
+    @GuardedBy("mBluetoothAudioStateLock")
+    private void reapplyAudioHalBluetoothState() {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "reapplyAudioHalBluetoothState() mBluetoothScoOnApplied: "
+                    + mBluetoothScoOnApplied + ", mBluetoothA2dpSuspendedApplied: "
+                    + mBluetoothA2dpSuspendedApplied + ", mBluetoothLeSuspendedApplied: "
+                    + mBluetoothLeSuspendedApplied);
+        }
+        // Note: the order of parameters is important.
+        if (mBluetoothScoOnApplied) {
+            AudioSystem.setParameters("A2dpSuspended=true");
+            AudioSystem.setParameters("LeAudioSuspended=true");
+            AudioSystem.setParameters("BT_SCO=on");
+        } else {
+            AudioSystem.setParameters("BT_SCO=off");
+            if (mBluetoothA2dpSuspendedApplied) {
+                AudioSystem.setParameters("A2dpSuspended=true");
+            } else {
+                AudioSystem.setParameters("A2dpSuspended=false");
+            }
+            if (mBluetoothLeSuspendedApplied) {
+                AudioSystem.setParameters("LeAudioSuspended=true");
+            } else {
+                AudioSystem.setParameters("LeAudioSuspended=false");
+            }
+        }
+    }
+
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
@@ -1727,6 +1753,9 @@
                             initRoutingStrategyIds();
                             updateActiveCommunicationDevice();
                             mDeviceInventory.onRestoreDevices();
+                            synchronized (mBluetoothAudioStateLock) {
+                                reapplyAudioHalBluetoothState();
+                            }
                             mBtHelper.onAudioServerDiedRestoreA2dp();
                             updateCommunicationRoute("MSG_RESTORE_DEVICES");
                         }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 39d9b45..e9acce6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7629,7 +7629,6 @@
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
-        DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_HDMI);
     }
 
     /** only public for mocking/spying, do not call outside of AudioService */
diff --git a/services/core/java/com/android/server/broadcastradio/OWNERS b/services/core/java/com/android/server/broadcastradio/OWNERS
index d2bdd64..51a85e4 100644
--- a/services/core/java/com/android/server/broadcastradio/OWNERS
+++ b/services/core/java/com/android/server/broadcastradio/OWNERS
@@ -1,3 +1,3 @@
 xuweilin@google.com
 oscarazu@google.com
-keunyoung@google.com
+ericjeong@google.com
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index da38a344..534197b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -717,7 +717,10 @@
             // Programmed
             int programedInfo = params[offset] & 0x0F;
             if (isValidProgrammedInfo(programedInfo)) {
-                if (programedInfo == 0x09 || programedInfo == 0x0B) {
+                offset = offset + 1;
+                // Duration Available (2 bytes)
+                if ((programedInfo == 0x09 || programedInfo == 0x0B)
+                        && params.length - offset >= 2) {
                     durationAvailable = true;
                 } else {
                     return true;
@@ -727,16 +730,17 @@
             // Non programmed
             int nonProgramedErrorInfo = params[offset] & 0x0F;
             if (isValidNotProgrammedErrorInfo(nonProgramedErrorInfo)) {
-                if (nonProgramedErrorInfo == 0x0E) {
+                offset = offset + 1;
+                // Duration Available (2 bytes)
+                if (nonProgramedErrorInfo == 0x0E && params.length - offset >= 2) {
                     durationAvailable = true;
                 } else {
                     return true;
                 }
             }
         }
-        offset = offset + 1;
         // Duration Available (2 bytes)
-        if (durationAvailable && params.length - offset >= 2) {
+        if (durationAvailable) {
             return (isValidDurationHours(params[offset]) && isValidMinute(params[offset + 1]));
         }
         return false;
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
index aa638aa..e507c6b 100644
--- a/services/core/java/com/android/server/inputmethod/OWNERS
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -6,5 +6,8 @@
 fstern@google.com
 cosminbaies@google.com
 
+# Automotive
+kanant@google.com
+
 ogunwale@google.com #{LAST_RESORT_SUGGESTION}
 jjaggi@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0c2eee5..939423f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.locksettings;
 
+import static android.security.Flags.reportPrimaryAuthAttempts;
 import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
@@ -139,6 +140,7 @@
 import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.RebootEscrowListener;
 import com.android.internal.widget.VerifyCredentialResponse;
@@ -180,6 +182,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.StringJoiner;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -327,6 +330,9 @@
 
     private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>();
 
+    private final CopyOnWriteArrayList<LockSettingsStateListener> mLockSettingsStateListeners =
+            new CopyOnWriteArrayList<>();
+
     // 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.
@@ -2118,11 +2124,10 @@
             Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
             return;
         }
-        final UserInfo userInfo = mUserManager.getUserInfo(userId);
         final String userType = isUserSecure(userId) ? "secured" : "unsecured";
         final byte[] secret = sp.deriveFileBasedEncryptionKey();
         try {
-            mStorageManager.unlockCeStorage(userId, userInfo.serialNumber, secret);
+            mStorageManager.unlockCeStorage(userId, secret);
             Slogf.i(TAG, "Unlocked CE storage for %s user %d", userType, userId);
         } catch (RemoteException e) {
             Slogf.wtf(TAG, e, "Failed to unlock CE storage for %s user %d", userType, userId);
@@ -2343,9 +2348,24 @@
                 requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
             }
         }
+        if (reportPrimaryAuthAttempts()) {
+            final boolean success =
+                    response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
+            notifyLockSettingsStateListeners(success, userId);
+        }
         return response;
     }
 
+    private void notifyLockSettingsStateListeners(boolean success, int userId) {
+        for (LockSettingsStateListener listener : mLockSettingsStateListeners) {
+            if (success) {
+                listener.onAuthenticationSucceeded(userId);
+            } else {
+                listener.onAuthenticationFailed(userId);
+            }
+        }
+    }
+
     @Override
     public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
             int userId, @LockPatternUtils.VerifyFlag int flags) {
@@ -3663,6 +3683,18 @@
         public void refreshStrongAuthTimeout(int userId) {
             mStrongAuth.refreshStrongAuthTimeout(userId);
         }
+
+        @Override
+        public void registerLockSettingsStateListener(@NonNull LockSettingsStateListener listener) {
+            Objects.requireNonNull(listener, "listener cannot be null");
+            mLockSettingsStateListeners.add(listener);
+        }
+
+        @Override
+        public void unregisterLockSettingsStateListener(
+                @NonNull LockSettingsStateListener listener) {
+            mLockSettingsStateListeners.remove(listener);
+        }
     }
 
     private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks {
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index cc205d4..cc58f38 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -1541,8 +1541,14 @@
      */
     public @NonNull AuthenticationResult unlockTokenBasedProtector(
             IGateKeeperService gatekeeper, long protectorId, byte[] token, int userId) {
-        SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME,
-                    protectorId, userId));
+        byte[] data = loadState(SP_BLOB_NAME, protectorId, userId);
+        if (data == null) {
+            AuthenticationResult result = new AuthenticationResult();
+            result.gkResponse = VerifyCredentialResponse.ERROR;
+            Slogf.w(TAG, "spblob not found for protector %016x, user %d", protectorId, userId);
+            return result;
+        }
+        SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(data);
         return unlockTokenBasedProtectorInternal(gatekeeper, protectorId, blob.mProtectorType,
                 token, userId);
     }
diff --git a/services/core/java/com/android/server/net/Android.bp b/services/core/java/com/android/server/net/Android.bp
new file mode 100644
index 0000000..71d8e6b
--- /dev/null
+++ b/services/core/java/com/android/server/net/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+    name: "net_flags",
+    package: "com.android.server.net",
+    srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "net_flags_lib",
+    aconfig_declarations: "net_flags",
+}
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 681d1a0..d25f529 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -17,6 +17,7 @@
 package com.android.server.net;
 
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -27,6 +28,7 @@
 import static android.net.INetd.FIREWALL_DENYLIST;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
@@ -187,6 +189,13 @@
      */
     @GuardedBy("mRulesLock")
     private final SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
+
+    /**
+     * Contains the per-UID firewall rules that are used when Background chain is enabled.
+     */
+    @GuardedBy("mRulesLock")
+    private final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+
     /** Set of states for the child firewall chains. True if the chain is active. */
     @GuardedBy("mRulesLock")
     final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -449,13 +458,15 @@
             syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
             syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted ");
             syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby ");
+            syncFirewallChainLocked(FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_NAME_BACKGROUND);
 
             final int[] chains = {
                     FIREWALL_CHAIN_STANDBY,
                     FIREWALL_CHAIN_DOZABLE,
                     FIREWALL_CHAIN_POWERSAVE,
                     FIREWALL_CHAIN_RESTRICTED,
-                    FIREWALL_CHAIN_LOW_POWER_STANDBY
+                    FIREWALL_CHAIN_LOW_POWER_STANDBY,
+                    FIREWALL_CHAIN_BACKGROUND,
             };
 
             for (int chain : chains) {
@@ -1206,6 +1217,8 @@
                 return FIREWALL_CHAIN_NAME_RESTRICTED;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return FIREWALL_CHAIN_NAME_BACKGROUND;
             default:
                 throw new IllegalArgumentException("Bad child chain: " + chain);
         }
@@ -1223,6 +1236,8 @@
                 return FIREWALL_ALLOWLIST;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return FIREWALL_ALLOWLIST;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return FIREWALL_ALLOWLIST;
             default:
                 return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
         }
@@ -1343,6 +1358,8 @@
                 return mUidFirewallRestrictedRules;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return mUidFirewallLowPowerStandbyRules;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return mUidFirewallBackgroundRules;
             case FIREWALL_CHAIN_NONE:
                 return mUidFirewallRules;
             default:
@@ -1395,6 +1412,10 @@
             pw.println(getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY));
             dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY,
                     mUidFirewallLowPowerStandbyRules);
+
+            pw.print("UID firewall background chain enabled: ");
+            pw.println(getFirewallChainState(FIREWALL_CHAIN_BACKGROUND));
+            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_BACKGROUND, mUidFirewallBackgroundRules);
         }
 
         pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
@@ -1494,6 +1515,11 @@
                 if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of low power standby");
                 return true;
             }
+            if (getFirewallChainState(FIREWALL_CHAIN_BACKGROUND)
+                    && mUidFirewallBackgroundRules.get(uid) != FIREWALL_RULE_ALLOW) {
+                if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because it is in background");
+                return true;
+            }
             if (mUidRejectOnMetered.get(uid)) {
                 if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
                         + " in the background");
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 4d19ead..8e2d778 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -16,6 +16,7 @@
 package com.android.server.net;
 
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -24,6 +25,7 @@
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
@@ -42,7 +44,6 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.annotations.Keep;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.RingBuffer;
 import com.android.server.am.ProcessList;
@@ -390,6 +391,8 @@
                 return FIREWALL_CHAIN_NAME_RESTRICTED;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return FIREWALL_CHAIN_NAME_BACKGROUND;
             default:
                 return String.valueOf(chain);
         }
@@ -414,7 +417,7 @@
         private static final Date sDate = new Date();
 
         public LogBuffer(int capacity) {
-            super(Data.class, capacity);
+            super(Data::new, Data[]::new, capacity);
         }
 
         public void uidStateChanged(int uid, int procState, long procStateSeq,
@@ -690,12 +693,8 @@
 
     /**
      * Container class for all networkpolicy events data.
-     *
-     * Note: This class needs to be public for RingBuffer class to be able to create
-     * new instances of this.
      */
-    @Keep
-    public static final class Data {
+    private static final class Data {
         public int type;
         public long timeStamp;
 
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 52734a4..b47458b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -47,6 +47,7 @@
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
@@ -54,6 +55,7 @@
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -77,6 +79,7 @@
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NOT_IN_BACKGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
@@ -96,6 +99,7 @@
 import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
 import static android.net.NetworkPolicyManager.allowedReasonsToString;
 import static android.net.NetworkPolicyManager.blockedReasonsToString;
+import static android.net.NetworkPolicyManager.isProcStateAllowedNetworkWhileBackground;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileInLowPowerStandby;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
@@ -201,12 +205,12 @@
 import android.os.MessageQueue.IdleHandler;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
+import android.os.PowerExemptionManager;
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
-import android.os.PowerWhitelistManager;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -242,6 +246,7 @@
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.util.SparseSetArray;
+import android.util.TimeUtils;
 import android.util.Xml;
 
 import com.android.internal.R;
@@ -457,6 +462,12 @@
      */
     private static final int MSG_UIDS_BLOCKED_REASONS_CHANGED = 23;
 
+    /**
+     * Message to update background restriction rules for uids that should lose network access
+     * due to being in the background.
+     */
+    private static final int MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS = 24;
+
     private static final int UID_MSG_STATE_CHANGED = 100;
     private static final int UID_MSG_GONE = 101;
 
@@ -475,7 +486,7 @@
 
     private ConnectivityManager mConnManager;
     private PowerManagerInternal mPowerManagerInternal;
-    private PowerWhitelistManager mPowerWhitelistManager;
+    private PowerExemptionManager mPowerExemptionManager;
     @NonNull
     private final Dependencies mDeps;
 
@@ -490,6 +501,12 @@
     // Denotes the status of restrict background read from disk.
     private boolean mLoadedRestrictBackground;
 
+    /**
+     * Whether or not network for apps in proc-states greater than
+     * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} is always blocked.
+     */
+    private boolean mBackgroundNetworkRestricted;
+
     // See main javadoc for instructions on how to use these locks.
     final Object mUidRulesFirstLock = new Object();
     final Object mNetworkPoliciesSecondLock = new Object();
@@ -514,6 +531,15 @@
 
     private volatile boolean mNetworkManagerReady;
 
+    /**
+     * Delay after which a uid going into a process state greater than or equal to
+     * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} will lose network access.
+     * The delay is meant to prevent churn due to quick process-state changes.
+     * Note that there is no delay while granting network access.
+     */
+    @VisibleForTesting
+    long mBackgroundRestrictionDelayMs = TimeUnit.SECONDS.toMillis(5);
+
     /** Defined network policies. */
     @GuardedBy("mNetworkPoliciesSecondLock")
     final ArrayMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = new ArrayMap<>();
@@ -545,6 +571,8 @@
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
     @GuardedBy("mUidRulesFirstLock")
+    final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+    @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray();
@@ -624,6 +652,14 @@
     @GuardedBy("mUidRulesFirstLock")
     private final SparseArray<UidBlockedState> mTmpUidBlockedState = new SparseArray<>();
 
+    /**
+     * Stores a map of uids to the time their transition to background is considered complete. They
+     * will lose network access after this time. This is used to prevent churn in rules due to quick
+     * process-state transitions.
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    private final SparseLongArray mBackgroundTransitioningUids = new SparseLongArray();
+
     /** Map from network ID to last observed meteredness state */
     @GuardedBy("mNetworkPoliciesSecondLock")
     private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
@@ -823,7 +859,7 @@
         mContext = Objects.requireNonNull(context, "missing context");
         mActivityManager = Objects.requireNonNull(activityManager, "missing activityManager");
         mNetworkManager = Objects.requireNonNull(networkManagement, "missing networkManagement");
-        mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
+        mPowerExemptionManager = mContext.getSystemService(PowerExemptionManager.class);
         mClock = Objects.requireNonNull(clock, "missing Clock");
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
@@ -859,15 +895,15 @@
 
     @GuardedBy("mUidRulesFirstLock")
     private void updatePowerSaveAllowlistUL() {
-        int[] whitelist = mPowerWhitelistManager.getWhitelistedAppIds(/* includingIdle */ false);
+        int[] allowlist = mPowerExemptionManager.getAllowListedAppIds(/* includingIdle */ false);
         mPowerSaveWhitelistExceptIdleAppIds.clear();
-        for (int uid : whitelist) {
+        for (int uid : allowlist) {
             mPowerSaveWhitelistExceptIdleAppIds.put(uid, true);
         }
 
-        whitelist = mPowerWhitelistManager.getWhitelistedAppIds(/* includingIdle */ true);
+        allowlist = mPowerExemptionManager.getAllowListedAppIds(/* includingIdle */ true);
         mPowerSaveWhitelistAppIds.clear();
-        for (int uid : whitelist) {
+        for (int uid : allowlist) {
             mPowerSaveWhitelistAppIds.put(uid, true);
         }
     }
@@ -1017,6 +1053,14 @@
                         writePolicyAL();
                     }
 
+                    // The flag is boot-stable.
+                    mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
+                    if (mBackgroundNetworkRestricted) {
+                        // Firewall rules and UidBlockedState will get updated in
+                        // updateRulesForGlobalChangeAL below.
+                        enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true);
+                    }
+
                     setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service");
                     updateRulesForGlobalChangeAL(false);
                     updateNotificationsNL();
@@ -1027,17 +1071,22 @@
                 final int changes = ActivityManager.UID_OBSERVER_PROCSTATE
                         | ActivityManager.UID_OBSERVER_GONE
                         | ActivityManager.UID_OBSERVER_CAPABILITY;
+
+                final int cutpoint = mBackgroundNetworkRestricted ? PROCESS_STATE_UNKNOWN
+                        : NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE;
+                // TODO (b/319728914): Filter out the unnecessary changes when using no cutpoint.
+
                 mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes,
-                        NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE, "android");
+                        cutpoint, "android");
                 mNetworkManager.registerObserver(mAlertObserver);
             } catch (RemoteException e) {
                 // ignored; both services live in system_server
             }
 
             // listen for changes to power save allowlist
-            final IntentFilter whitelistFilter = new IntentFilter(
+            final IntentFilter allowlistFilter = new IntentFilter(
                     PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
-            mContext.registerReceiver(mPowerSaveWhitelistReceiver, whitelistFilter, null, mHandler);
+            mContext.registerReceiver(mPowerSaveAllowlistReceiver, allowlistFilter, null, mHandler);
 
             // watch for network interfaces to be claimed
             final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
@@ -1188,12 +1237,15 @@
         }
     }
 
-    final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mPowerSaveAllowlistReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and POWER_SAVE_WHITELIST_CHANGED is protected
             synchronized (mUidRulesFirstLock) {
                 updatePowerSaveAllowlistUL();
+                if (mBackgroundNetworkRestricted) {
+                    updateRulesForBackgroundChainUL();
+                }
                 updateRulesForRestrictPowerUL();
                 updateRulesForAppIdleUL();
             }
@@ -3901,6 +3953,11 @@
                 }
 
                 fout.println();
+                fout.println("Flags:");
+                fout.println("Network blocked for TOP_SLEEPING and above: "
+                        + mBackgroundNetworkRestricted);
+
+                fout.println();
                 fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
                 fout.println("mRestrictBackgroundBeforeBsm: " + mRestrictBackgroundBeforeBsm);
                 fout.println("mLoadedRestrictBackground: " + mLoadedRestrictBackground);
@@ -4042,6 +4099,22 @@
                     fout.decreaseIndent();
                 }
 
+                size = mBackgroundTransitioningUids.size();
+                if (size > 0) {
+                    final long nowUptime = SystemClock.uptimeMillis();
+                    fout.println("Uids transitioning to background:");
+                    fout.increaseIndent();
+                    for (int i = 0; i < size; i++) {
+                        fout.print("UID=");
+                        fout.print(mBackgroundTransitioningUids.keyAt(i));
+                        fout.print(", ");
+                        TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i), nowUptime,
+                                fout);
+                        fout.println();
+                    }
+                    fout.decreaseIndent();
+                }
+
                 final SparseBooleanArray knownUids = new SparseBooleanArray();
                 collectKeys(mUidState, knownUids);
                 synchronized (mUidBlockedState) {
@@ -4163,6 +4236,12 @@
         return isProcStateAllowedWhileInLowPowerStandby(uidState);
     }
 
+    @GuardedBy("mUidRulesFirstLock")
+    private boolean isUidExemptFromBackgroundRestrictions(int uid) {
+        return mBackgroundTransitioningUids.indexOfKey(uid) >= 0
+                || isProcStateAllowedNetworkWhileBackground(mUidState.get(uid));
+    }
+
     /**
      * Process state of UID changed; if needed, will trigger
      * {@link #updateRulesForDataUsageRestrictionsUL(int)} and
@@ -4188,6 +4267,8 @@
                 // state changed, push updated rules
                 mUidState.put(uid, newUidState);
                 updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, newUidState);
+
+                boolean updatePowerRestrictionRules = false;
                 boolean allowedWhileIdleOrPowerSaveModeChanged =
                         isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
                                 != isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState);
@@ -4199,19 +4280,44 @@
                     if (mRestrictPower) {
                         updateRuleForRestrictPowerUL(uid);
                     }
-                    updateRulesForPowerRestrictionsUL(uid, procState);
+                    updatePowerRestrictionRules = true;
+                }
+                if (mBackgroundNetworkRestricted) {
+                    final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground(
+                            oldUidState);
+                    final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState);
+                    if (!wasAllowed && isAllowed) {
+                        mBackgroundTransitioningUids.delete(uid);
+                        updateRuleForBackgroundUL(uid);
+                        updatePowerRestrictionRules = true;
+                    } else if (wasAllowed && !isAllowed) {
+                        final long completionTimeMs = SystemClock.uptimeMillis()
+                                + mBackgroundRestrictionDelayMs;
+                        if (mBackgroundTransitioningUids.indexOfKey(uid) < 0) {
+                            // This is just a defensive check in case the upstream code ever makes
+                            // multiple calls for the same process state change.
+                            mBackgroundTransitioningUids.put(uid, completionTimeMs);
+                        }
+                        if (!mHandler.hasMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS)) {
+                            // Many uids may be in this "transitioning" state at the same time, so
+                            // using one message at a time to avoid congestion in the MessageQueue.
+                            mHandler.sendEmptyMessageAtTime(
+                                    MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs);
+                        }
+                    }
                 }
                 if (mLowPowerStandbyActive) {
                     boolean allowedInLpsChanged =
                             isProcStateAllowedWhileInLowPowerStandby(oldUidState)
                                     != isProcStateAllowedWhileInLowPowerStandby(newUidState);
                     if (allowedInLpsChanged) {
-                        if (!allowedWhileIdleOrPowerSaveModeChanged) {
-                            updateRulesForPowerRestrictionsUL(uid, procState);
-                        }
                         updateRuleForLowPowerStandbyUL(uid);
+                        updatePowerRestrictionRules = true;
                     }
                 }
+                if (updatePowerRestrictionRules) {
+                    updateRulesForPowerRestrictionsUL(uid, procState);
+                }
                 return true;
             }
         } finally {
@@ -4234,6 +4340,12 @@
                 if (mRestrictPower) {
                     updateRuleForRestrictPowerUL(uid);
                 }
+                if (mBackgroundNetworkRestricted) {
+                    // Uid is no longer running, there is no point in any grace period of network
+                    // access during transitions to lower importance proc-states.
+                    mBackgroundTransitioningUids.delete(uid);
+                    updateRuleForBackgroundUL(uid);
+                }
                 updateRulesForPowerRestrictionsUL(uid);
                 if (mLowPowerStandbyActive) {
                     updateRuleForLowPowerStandbyUL(uid);
@@ -4441,11 +4553,41 @@
         }
     }
 
+    /**
+     * Updates the rules for apps allowlisted to use network while in the background.
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    private void updateRulesForBackgroundChainUL() {
+        Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForBackgroundChainUL");
+        try {
+            final SparseIntArray uidRules = mUidFirewallBackgroundRules;
+            uidRules.clear();
+
+            final List<UserInfo> users = mUserManager.getUsers();
+            for (int ui = users.size() - 1; ui >= 0; ui--) {
+                final UserInfo user = users.get(ui);
+                updateRulesForAllowlistedAppIds(uidRules, mPowerSaveTempWhitelistAppIds, user.id);
+                updateRulesForAllowlistedAppIds(uidRules, mPowerSaveWhitelistAppIds, user.id);
+                updateRulesForAllowlistedAppIds(uidRules, mPowerSaveWhitelistExceptIdleAppIds,
+                        user.id);
+            }
+            for (int i = mUidState.size() - 1; i >= 0; i--) {
+                if (mBackgroundTransitioningUids.indexOfKey(mUidState.keyAt(i)) >= 0
+                        || isProcStateAllowedNetworkWhileBackground(mUidState.valueAt(i))) {
+                    uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
+                }
+            }
+            setUidFirewallRulesUL(FIREWALL_CHAIN_BACKGROUND, uidRules);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_NETWORK);
+        }
+    }
+
     private void updateRulesForAllowlistedAppIds(final SparseIntArray uidRules,
-            final SparseBooleanArray whitelistedAppIds, int userId) {
-        for (int i = whitelistedAppIds.size() - 1; i >= 0; --i) {
-            if (whitelistedAppIds.valueAt(i)) {
-                final int appId = whitelistedAppIds.keyAt(i);
+            final SparseBooleanArray allowlistedAppIds, int userId) {
+        for (int i = allowlistedAppIds.size() - 1; i >= 0; --i) {
+            if (allowlistedAppIds.valueAt(i)) {
+                final int appId = allowlistedAppIds.keyAt(i);
                 final int uid = UserHandle.getUid(userId, appId);
                 uidRules.put(uid, FIREWALL_RULE_ALLOW);
             }
@@ -4504,12 +4646,12 @@
     @GuardedBy("mUidRulesFirstLock")
     private boolean isAllowlistedFromPowerSaveUL(int uid, boolean deviceIdleMode) {
         final int appId = UserHandle.getAppId(uid);
-        boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId)
+        boolean allowlisted = mPowerSaveTempWhitelistAppIds.get(appId)
                 || mPowerSaveWhitelistAppIds.get(appId);
         if (!deviceIdleMode) {
-            isWhitelisted = isWhitelisted || isAllowlistedFromPowerSaveExceptIdleUL(uid);
+            allowlisted = allowlisted || isAllowlistedFromPowerSaveExceptIdleUL(uid);
         }
-        return isWhitelisted;
+        return allowlisted;
     }
 
     /**
@@ -4598,6 +4740,38 @@
     }
 
     /**
+     * Update firewall rule for a single uid whenever there are any interesting changes in the uid.
+     * Currently, it is called when:
+     * - The uid is added to or removed from power allowlists
+     * - The uid undergoes a process-state change
+     * - A package belonging to this uid is added
+     * - The uid is evicted from memory
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    void updateRuleForBackgroundUL(int uid) {
+        if (!isUidValidForAllowlistRulesUL(uid)) {
+            return;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForBackgroundUL: " + uid);
+        try {
+            // The uid should be absent from mUidState and mBackgroundTransitioningUids if it is
+            // not running when this method is called. Then, the firewall state will depend on the
+            // allowlist alone. This is the desired behavior.
+            if (isAllowlistedFromPowerSaveUL(uid, false)
+                    || isUidExemptFromBackgroundRestrictions(uid)) {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_ALLOW);
+                if (LOGD) Log.d(TAG, "updateRuleForBackgroundUL ALLOW " + uid);
+            } else {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DEFAULT);
+                if (LOGD) Log.d(TAG, "updateRuleForBackgroundUL " + uid + " to DEFAULT");
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+        }
+    }
+
+    /**
      * Toggle the firewall standby chain and inform listeners if the uid rules have effectively
      * changed.
      */
@@ -4644,6 +4818,9 @@
                     "updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-"));
         }
         try {
+            if (mBackgroundNetworkRestricted) {
+                updateRulesForBackgroundChainUL();
+            }
             updateRulesForAppIdleUL();
             updateRulesForRestrictPowerUL();
             updateRulesForRestrictBackgroundUL();
@@ -4803,6 +4980,9 @@
             updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
             updateRuleForDeviceIdleUL(uid);
             updateRuleForRestrictPowerUL(uid);
+            if (mBackgroundNetworkRestricted) {
+                updateRuleForBackgroundUL(uid);
+            }
             // Update internal rules.
             updateRulesForPowerRestrictionsUL(uid);
         }
@@ -4940,6 +5120,8 @@
         mUidFirewallStandbyRules.delete(uid);
         mUidFirewallDozableRules.delete(uid);
         mUidFirewallPowerSaveRules.delete(uid);
+        mUidFirewallBackgroundRules.delete(uid);
+        mBackgroundTransitioningUids.delete(uid);
         mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
         mPowerSaveWhitelistAppIds.delete(uid);
         mPowerSaveTempWhitelistAppIds.delete(uid);
@@ -4973,6 +5155,9 @@
         updateRuleForDeviceIdleUL(uid);
         updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
         updateRuleForRestrictPowerUL(uid);
+        if (mBackgroundNetworkRestricted) {
+            updateRuleForBackgroundUL(uid);
+        }
 
         // If the uid has the necessary permissions, then it should be added to the restricted mode
         // firewall allowlist.
@@ -5157,7 +5342,6 @@
      * Similar to above but ignores idle state if app standby is currently disabled by parole.
      *
      * @param uid the uid of the app to update rules for
-     * @param oldUidRules the current rules for the uid, in order to determine if there's a change
      * @param isUidIdle whether uid is idle or not
      */
     @GuardedBy("mUidRulesFirstLock")
@@ -5203,6 +5387,7 @@
             newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0);
             newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0);
             newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE);
+            newBlockedReasons |= mBackgroundNetworkRestricted ? BLOCKED_REASON_APP_BACKGROUND : 0;
 
             newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
             newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
@@ -5215,6 +5400,9 @@
                     & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
             newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid))
                     ? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0;
+            newAllowedReasons |= (mBackgroundNetworkRestricted
+                    && isUidExemptFromBackgroundRestrictions(uid))
+                    ? ALLOWED_REASON_NOT_IN_BACKGROUND : 0;
 
             uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
                     & BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
@@ -5236,7 +5424,7 @@
 
             oldEffectiveBlockedReasons = previousUidBlockedState.effectiveBlockedReasons;
             newEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
-            uidRules = oldEffectiveBlockedReasons == newEffectiveBlockedReasons
+            uidRules = (oldEffectiveBlockedReasons == newEffectiveBlockedReasons)
                     ? RULE_NONE
                     : uidBlockedState.deriveUidRules();
         }
@@ -5429,6 +5617,28 @@
                     mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
                     return true;
                 }
+                case MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS: {
+                    final long now = SystemClock.uptimeMillis();
+                    long nextCheckTime = Long.MAX_VALUE;
+                    synchronized (mUidRulesFirstLock) {
+                        for (int i = mBackgroundTransitioningUids.size() - 1; i >= 0; i--) {
+                            final long completionTimeMs = mBackgroundTransitioningUids.valueAt(i);
+                            if (completionTimeMs > now) {
+                                nextCheckTime = Math.min(nextCheckTime, completionTimeMs);
+                                continue;
+                            }
+                            final int uid = mBackgroundTransitioningUids.keyAt(i);
+                            mBackgroundTransitioningUids.removeAt(i);
+                            updateRuleForBackgroundUL(uid);
+                            updateRulesForPowerRestrictionsUL(uid, false);
+                        }
+                    }
+                    if (nextCheckTime < Long.MAX_VALUE) {
+                        mHandler.sendEmptyMessageAtTime(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS,
+                                nextCheckTime);
+                    }
+                    return true;
+                }
                 case MSG_POLICIES_CHANGED: {
                     final int uid = msg.arg1;
                     final int policy = msg.arg2;
@@ -5840,6 +6050,8 @@
                 mUidFirewallRestrictedModeRules.put(uid, rule);
             } else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) {
                 mUidFirewallLowPowerStandbyModeRules.put(uid, rule);
+            } else if (chain == FIREWALL_CHAIN_BACKGROUND) {
+                mUidFirewallBackgroundRules.put(uid, rule);
             }
 
             try {
@@ -5896,6 +6108,8 @@
                     FIREWALL_RULE_DEFAULT);
             mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid,
                     FIREWALL_RULE_DEFAULT);
+            mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, uid,
+                    FIREWALL_RULE_DEFAULT);
             mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
             mLogger.meteredAllowlistChanged(uid, false);
             mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
@@ -6420,10 +6634,12 @@
                 effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+                effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
             }
             if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+                effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
             }
             if ((allowedReasons & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_RESTRICTED_MODE;
@@ -6434,19 +6650,24 @@
             if ((allowedReasons & ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY;
             }
+            if ((allowedReasons & ALLOWED_REASON_NOT_IN_BACKGROUND) != 0) {
+                effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
+            }
 
             return effectiveBlockedReasons;
         }
 
         static int getAllowedReasonsForProcState(int procState) {
-            if (procState > NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE) {
-                return ALLOWED_REASON_NONE;
-            } else if (procState <= NetworkPolicyManager.TOP_THRESHOLD_STATE) {
+            if (procState <= NetworkPolicyManager.TOP_THRESHOLD_STATE) {
                 return ALLOWED_REASON_TOP | ALLOWED_REASON_FOREGROUND
-                        | ALLOWED_METERED_REASON_FOREGROUND;
-            } else {
-                return ALLOWED_REASON_FOREGROUND | ALLOWED_METERED_REASON_FOREGROUND;
+                        | ALLOWED_METERED_REASON_FOREGROUND | ALLOWED_REASON_NOT_IN_BACKGROUND;
+            } else if (procState <= NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE) {
+                return ALLOWED_REASON_FOREGROUND | ALLOWED_METERED_REASON_FOREGROUND
+                        | ALLOWED_REASON_NOT_IN_BACKGROUND;
+            } else if (procState < NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE) {
+                return ALLOWED_REASON_NOT_IN_BACKGROUND;
             }
+            return ALLOWED_REASON_NONE;
         }
 
         @Override
@@ -6471,6 +6692,7 @@
                 BLOCKED_REASON_APP_STANDBY,
                 BLOCKED_REASON_RESTRICTED_MODE,
                 BLOCKED_REASON_LOW_POWER_STANDBY,
+                BLOCKED_REASON_APP_BACKGROUND,
                 BLOCKED_METERED_REASON_DATA_SAVER,
                 BLOCKED_METERED_REASON_USER_RESTRICTED,
                 BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -6484,6 +6706,7 @@
                 ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST,
                 ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS,
                 ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST,
+                ALLOWED_REASON_NOT_IN_BACKGROUND,
                 ALLOWED_METERED_REASON_USER_EXEMPTED,
                 ALLOWED_METERED_REASON_SYSTEM,
                 ALLOWED_METERED_REASON_FOREGROUND,
@@ -6503,6 +6726,8 @@
                     return "RESTRICTED_MODE";
                 case BLOCKED_REASON_LOW_POWER_STANDBY:
                     return "LOW_POWER_STANDBY";
+                case BLOCKED_REASON_APP_BACKGROUND:
+                    return "APP_BACKGROUND";
                 case BLOCKED_METERED_REASON_DATA_SAVER:
                     return "DATA_SAVER";
                 case BLOCKED_METERED_REASON_USER_RESTRICTED:
@@ -6533,6 +6758,8 @@
                     return "RESTRICTED_MODE_PERMISSIONS";
                 case ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST:
                     return "LOW_POWER_STANDBY_ALLOWLIST";
+                case ALLOWED_REASON_NOT_IN_BACKGROUND:
+                    return "NOT_IN_BACKGROUND";
                 case ALLOWED_METERED_REASON_USER_EXEMPTED:
                     return "METERED_USER_EXEMPTED";
                 case ALLOWED_METERED_REASON_SYSTEM:
@@ -6600,7 +6827,8 @@
             int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY
                     | BLOCKED_REASON_DOZE
                     | BLOCKED_REASON_BATTERY_SAVER
-                    | BLOCKED_REASON_LOW_POWER_STANDBY;
+                    | BLOCKED_REASON_LOW_POWER_STANDBY
+                    | BLOCKED_REASON_APP_BACKGROUND;
             if ((effectiveBlockedReasons & powerBlockedReasons) != 0) {
                 uidRule |= RULE_REJECT_ALL;
             } else if ((blockedReasons & powerBlockedReasons) != 0) {
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
new file mode 100644
index 0000000..419665a
--- /dev/null
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.net"
+
+flag {
+    name: "network_blocked_for_top_sleeping_and_above"
+    namespace: "backstage_power"
+    description: "Block network access for apps in a low importance background state"
+    bug: "304347838"
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e633ba6..db04093 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8237,19 +8237,26 @@
         }
     }
 
+    private PendingIntent getNotificationTimeoutPendingIntent(NotificationRecord record,
+            int flags) {
+        flags |= PendingIntent.FLAG_IMMUTABLE;
+        return PendingIntent.getBroadcast(getContext(),
+                REQUEST_CODE_TIMEOUT,
+                new Intent(ACTION_NOTIFICATION_TIMEOUT)
+                        .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
+                        .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
+                                .appendPath(record.getKey()).build())
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                        .putExtra(EXTRA_KEY, record.getKey()),
+                flags);
+    }
+
     @VisibleForTesting
     @GuardedBy("mNotificationLock")
     void scheduleTimeoutLocked(NotificationRecord record) {
         if (record.getNotification().getTimeoutAfter() > 0) {
-            final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
-                    REQUEST_CODE_TIMEOUT,
-                    new Intent(ACTION_NOTIFICATION_TIMEOUT)
-                            .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
-                            .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
-                                    .appendPath(record.getKey()).build())
-                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                            .putExtra(EXTRA_KEY, record.getKey()),
-                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+            final PendingIntent pi = getNotificationTimeoutPendingIntent(
+                    record, PendingIntent.FLAG_UPDATE_CURRENT);
             mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     SystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi);
         }
@@ -8257,6 +8264,16 @@
 
     @VisibleForTesting
     @GuardedBy("mNotificationLock")
+    void cancelScheduledTimeoutLocked(NotificationRecord record) {
+        final PendingIntent pi = getNotificationTimeoutPendingIntent(
+                record, PendingIntent.FLAG_CANCEL_CURRENT);
+        if (pi != null) {
+            mAlarmManager.cancel(pi);
+        }
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mNotificationLock")
     /**
      * Determine whether this notification should attempt to make noise, vibrate, or flash the LED
      * @return buzzBeepBlink - bitfield (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)
@@ -9249,21 +9266,7 @@
             int rank, int count, boolean wasPosted, String listenerName,
             @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         final String canceledKey = r.getKey();
-
-        // Get pending intent used to create alarm, use FLAG_NO_CREATE if PendingIntent
-        // does not already exist, then null will be returned.
-        final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
-                REQUEST_CODE_TIMEOUT,
-                new Intent(ACTION_NOTIFICATION_TIMEOUT)
-                        .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
-                                .appendPath(r.getKey()).build())
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
-                PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
-
-        // Cancel alarm corresponding to pi.
-        if (pi != null) {
-            mAlarmManager.cancel(pi);
-        }
+        cancelScheduledTimeoutLocked(r);
 
         // Record caller.
         recordCallerLocked(r);
diff --git a/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
new file mode 100644
index 0000000..baa41a5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
@@ -0,0 +1,2 @@
+georgechan@google.com
+wenhaowang@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 84324f2..c8bc56c 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -51,3 +51,5 @@
 per-file ShortcutService.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
 per-file ShortcutUser.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
 
+# background install control service
+per-file BackgroundInstall* = file:BACKGROUND_INSTALL_OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 178719f..7c307bd 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -42,6 +42,7 @@
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IPackageInstallerSession;
@@ -745,6 +746,22 @@
             params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION;
         }
 
+        if (Flags.rollbackLifetime()) {
+            if (params.rollbackLifetimeMillis > 0) {
+                if ((params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) == 0) {
+                    throw new IllegalArgumentException(
+                            "Can't set rollbackLifetimeMillis when rollback is not enabled");
+                }
+                if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException(
+                            "Setting rollback lifetime requires the MANAGE_ROLLBACKS permission");
+                }
+            } else if (params.rollbackLifetimeMillis < 0) {
+                throw new IllegalArgumentException("rollbackLifetimeMillis can't be negative.");
+            }
+        }
+
         boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
         if (isApex) {
             if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES)
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2372fd3..94c6b8b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1206,6 +1206,7 @@
             info.whitelistedRestrictedPermissions = params.whitelistedRestrictedPermissions;
             info.autoRevokePermissionsMode = params.autoRevokePermissionsMode;
             info.installFlags = params.installFlags;
+            info.rollbackLifetimeMillis = params.rollbackLifetimeMillis;
             info.isMultiPackage = params.isMultiPackage;
             info.isStaged = params.isStaged;
             info.rollbackDataPolicy = params.rollbackDataPolicy;
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 8961923..d9d7087 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -193,7 +193,7 @@
             }
 
             try {
-                sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber, flags);
+                sm.prepareUserStorage(volumeUuid, user.id, flags);
                 synchronized (mPm.mInstallLock) {
                     appDataHelper.reconcileAppsDataLI(volumeUuid, user.id, flags,
                             true /* migrateAppData */);
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 8adb566..1d41401 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -92,7 +92,7 @@
                 volumeUuid, userId, flags, isNewUser);
         try {
             // Prepare CE and/or DE storage.
-            storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
+            storage.prepareUserStorage(volumeUuid, userId, flags);
 
             // Ensure that the data directories of a removed user with the same ID are not being
             // reused.  New users must get fresh data directories, to avoid leaking data.
@@ -141,7 +141,7 @@
                 // If internal storage of the system user fails to prepare on first boot, then
                 // things are *really* broken, so we might as well reboot to recovery right away.
                 try {
-                    Log.wtf(TAG, "prepareUserData failed for user " + userId, e);
+                    Log.e(TAG, "prepareUserData failed for user " + userId, e);
                     if (isNewUser && userId == UserHandle.USER_SYSTEM && volumeUuid == null) {
                         RecoverySystem.rebootPromptAndWipeUserData(mContext,
                                 "failed to prepare internal storage for system user");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 062d797..8fcc921 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4920,7 +4920,7 @@
 
             t.traceBegin("createUserStorageKeys");
             final StorageManager storage = mContext.getSystemService(StorageManager.class);
-            storage.createUserStorageKeys(userId, userInfo.serialNumber, userInfo.isEphemeral());
+            storage.createUserStorageKeys(userId, userInfo.isEphemeral());
             t.traceEnd();
 
             // Only prepare DE storage here.  CE storage will be prepared later, when the user is
diff --git a/services/core/java/com/android/server/pm/verify/domain/OWNERS b/services/core/java/com/android/server/pm/verify/domain/OWNERS
index c669112..b451fe4 100644
--- a/services/core/java/com/android/server/pm/verify/domain/OWNERS
+++ b/services/core/java/com/android/server/pm/verify/domain/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 36137
+include /PACKAGE_MANAGER_OWNERS
 
-chiuwinson@google.com
-patb@google.com
-toddke@google.com
\ No newline at end of file
+wloh@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 9d5173a..69b7034 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -524,6 +524,7 @@
         if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
         synchronized (sRequestLock) {
             if (!setupOrClearBcb(true, command)) {
+                Slog.e(TAG, "rebootRecoveryWithCommand failed to setup BCB");
                 return;
             }
 
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
index 141d4dc..9ee9b14 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
@@ -48,6 +48,8 @@
                     return isLskfCaptured();
                 case "reboot-and-apply":
                     return rebootAndApply();
+                case "wipe":
+                    return wipe();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -58,6 +60,18 @@
         }
     }
 
+    private int wipe() throws RemoteException {
+        PrintWriter pw = getOutPrintWriter();
+        String newFsType = getNextArg();
+        String command = "--wipe_data";
+        if (newFsType != null && !newFsType.isEmpty()) {
+            command += "\n--reformat_data=" + newFsType;
+        }
+        pw.println("Rebooting into recovery with " + command.replaceAll("\n", " "));
+        mService.rebootRecoveryWithCommand(command);
+        return 0;
+    }
+
     private int requestLskf() throws RemoteException {
         String packageName = getNextArgRequired();
         boolean success = mService.requestLskf(packageName, null);
@@ -104,5 +118,6 @@
         pw.println("  clear-lskf");
         pw.println("  is-lskf-captured <package_name>");
         pw.println("  reboot-and-apply <package_name> <reason>");
+        pw.println("  wipe <new filesystem type ext4/f2fs>");
     }
 }
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 85d93f4..a5b90f1 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -174,6 +174,11 @@
     @Nullable private final String mInstallerPackageName;
 
     /**
+     * Time after which rollback expires.
+     */
+    private long mRollbackLifetimeMillis = 0;
+
+    /**
      * Session ids for all packages in the install. For multi-package sessions, this is the list
      * of child session ids. For normal sessions, this list is a single element with the normal
      * session id.
@@ -286,6 +291,24 @@
     }
 
     /**
+     * Sets rollback lifetime in milliseconds, for purposes of expiring rollback data.
+     */
+    @WorkerThread
+    void setRollbackLifetimeMillis(long lifetimeMillis) {
+        assertInWorkerThread();
+        mRollbackLifetimeMillis = lifetimeMillis;
+    }
+
+    /**
+     * Returns rollback lifetime in milliseconds, for purposes of expiring rollback data.
+     */
+    @WorkerThread
+    long getRollbackLifetimeMillis() {
+        assertInWorkerThread();
+        return mRollbackLifetimeMillis;
+    }
+
+    /**
      * Returns the session ID associated with this rollback, or {@code -1} if unknown.
      */
     @AnyThread
@@ -930,6 +953,7 @@
         ipw.println("-state: " + getStateAsString());
         ipw.println("-stateDescription: " + mStateDescription);
         ipw.println("-timestamp: " + getTimestamp());
+        ipw.println("-rollbackLifetimeMillis: " + getRollbackLifetimeMillis());
         ipw.println("-isStaged: " + isStaged());
         ipw.println("-originalSessionId: " + getOriginalSessionId());
         ipw.println("-packages:");
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 720c773..b38d6c7 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -28,6 +28,7 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
@@ -702,6 +703,15 @@
     // Schedules future expiration as appropriate.
     @WorkerThread
     private void runExpiration() {
+        if (Flags.rollbackLifetime()) {
+            runExpirationCustomRollbackLifetime();
+        } else {
+            runExpirationDefaultRollbackLifetime();
+        }
+    }
+
+    @WorkerThread
+    private void runExpirationDefaultRollbackLifetime() {
         getHandler().removeCallbacks(mRunExpiration);
         assertInWorkerThread();
         Instant now = Instant.now();
@@ -729,6 +739,44 @@
         }
     }
 
+    @WorkerThread
+    private void runExpirationCustomRollbackLifetime() {
+        getHandler().removeCallbacks(mRunExpiration);
+        assertInWorkerThread();
+        Instant now = Instant.now();
+        long minDelay = 0;
+        Iterator<Rollback> iter = mRollbacks.iterator();
+        while (iter.hasNext()) {
+            Rollback rollback = iter.next();
+            if (!rollback.isAvailable() && !rollback.isCommitted()) {
+                continue;
+            }
+            long rollbackLifetimeMillis = rollback.getRollbackLifetimeMillis();
+            if (rollbackLifetimeMillis <= 0) {
+                rollbackLifetimeMillis = mRollbackLifetimeDurationInMillis;
+            }
+
+            Instant rollbackExpiryTimestamp = rollback.getTimestamp()
+                    .plusMillis(rollbackLifetimeMillis);
+            if (!now.isBefore(rollbackExpiryTimestamp)) {
+                Slog.i(TAG, "runExpiration id=" + rollback.info.getRollbackId());
+                iter.remove();
+                deleteRollback(rollback, "Expired by timeout");
+                continue;
+            }
+
+            long delay = now.until(
+                    rollbackExpiryTimestamp, ChronoUnit.MILLIS);
+            if (minDelay == 0 || delay < minDelay) {
+                minDelay = delay;
+            }
+        }
+
+        if (minDelay != 0) {
+            getHandler().postDelayed(mRunExpiration, minDelay);
+        }
+    }
+
     @AnyThread
     private Handler getHandler() {
         return mHandler;
@@ -1231,8 +1279,8 @@
                 ipw.println();
             }
 
-            PackageWatchdog.getInstance(mContext).dump(ipw);
         });
+        PackageWatchdog.getInstance(mContext).dump(ipw);
     }
 
     @AnyThread
@@ -1277,6 +1325,7 @@
         }
 
         final Rollback rollback;
+
         if (parentSession.isStaged()) {
             rollback = mRollbackStore.createStagedRollback(rollbackId, parentSessionId, userId,
                     installerPackageName, packageSessionIds, getExtensionVersions());
@@ -1285,6 +1334,10 @@
                     installerPackageName, packageSessionIds, getExtensionVersions());
         }
 
+        if (Flags.rollbackLifetime()) {
+            rollback.setRollbackLifetimeMillis(parentSession.rollbackLifetimeMillis);
+        }
+
         mRollbacks.add(rollback);
         return rollback;
     }
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 8068c6f..0af137f 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -19,6 +19,7 @@
 import static com.android.server.rollback.Rollback.rollbackStateFromString;
 
 import android.annotation.NonNull;
+import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.content.rollback.PackageRollbackInfo;
@@ -312,6 +313,9 @@
             JSONObject dataJson = new JSONObject();
             dataJson.put("info", rollbackInfoToJson(rollback.info));
             dataJson.put("timestamp", rollback.getTimestamp().toString());
+            if (Flags.rollbackLifetime()) {
+                dataJson.put("rollbackLifetimeMillis", rollback.getRollbackLifetimeMillis());
+            }
             dataJson.put("originalSessionId", rollback.getOriginalSessionId());
             dataJson.put("state", rollback.getStateAsString());
             dataJson.put("stateDescription", rollback.getStateDescription());
@@ -375,7 +379,7 @@
     @VisibleForTesting
     static Rollback rollbackFromJson(JSONObject dataJson, File backupDir)
             throws JSONException, ParseException {
-        return new Rollback(
+        Rollback rollback = new Rollback(
                 rollbackInfoFromJson(dataJson.getJSONObject("info")),
                 backupDir,
                 Instant.parse(dataJson.getString("timestamp")),
@@ -388,6 +392,10 @@
                 dataJson.optInt("userId", UserHandle.SYSTEM.getIdentifier()),
                 dataJson.optString("installerPackageName", ""),
                 extensionVersionsFromJson(dataJson.optJSONArray("extensionVersions")));
+        if (Flags.rollbackLifetime()) {
+            rollback.setRollbackLifetimeMillis(dataJson.optLong("rollbackLifetimeMillis"));
+        }
+        return rollback;
     }
 
     private static JSONObject toJson(VersionedPackage pkg) throws JSONException {
diff --git a/services/core/java/com/android/server/trust/TEST_MAPPING b/services/core/java/com/android/server/trust/TEST_MAPPING
index fa46acd..0de7c28 100644
--- a/services/core/java/com/android/server/trust/TEST_MAPPING
+++ b/services/core/java/com/android/server/trust/TEST_MAPPING
@@ -12,6 +12,19 @@
         ]
       }
     ],
+    "postsubmit": [
+      {
+        "name": "FrameworksMockingServicesTests",
+        "options": [
+          {
+            "include-filter": "com.android.server.trust"
+          },
+          {
+            "exclude-annotation": "androidx.test.filters.FlakyTest"
+          }
+        ]
+      }
+    ],
     "trust-tablet": [
       {
         "name": "TrustTests",
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 1ebad9b..ba8a76c 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -44,6 +44,9 @@
 import android.graphics.drawable.Drawable;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -75,6 +78,7 @@
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.DumpUtils;
@@ -156,6 +160,8 @@
     private final LockPatternUtils mLockPatternUtils;
     private final UserManager mUserManager;
     private final ActivityManager mActivityManager;
+    private FingerprintManager mFingerprintManager;
+    private FaceManager mFaceManager;
     private VirtualDeviceManagerInternal mVirtualDeviceManager;
 
     private enum TrustState {
@@ -293,6 +299,8 @@
             mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
             mReceiver.register(mContext);
             mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
+            mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+            mFaceManager = mContext.getSystemService(FaceManager.class);
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             mTrustAgentsCanRun = true;
             refreshAgentList(UserHandle.USER_ALL);
@@ -894,7 +902,19 @@
 
     private void notifyKeystoreOfDeviceLockState(int userId, boolean isLocked) {
         if (isLocked) {
-            Authorization.onDeviceLocked(userId, getBiometricSids(userId));
+            if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()) {
+                // A profile with unified challenge is unlockable not by its own biometrics and
+                // trust agents, but rather by those of the parent user.  Therefore, when protecting
+                // the profile's UnlockedDeviceRequired keys, we must use the parent's list of
+                // biometric SIDs and weak unlock methods, not the profile's.
+                int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
+                        ? resolveProfileParent(userId) : userId;
+
+                Authorization.onDeviceLocked(userId, getBiometricSids(authUserId),
+                        isWeakUnlockMethodEnabled(authUserId));
+            } else {
+                Authorization.onDeviceLocked(userId, getBiometricSids(userId), false);
+            }
         } else {
             // Notify Keystore that the device is now unlocked for the user.  Note that for unlocks
             // with LSKF, this is redundant with the call from LockSettingsService which provides
@@ -1444,6 +1464,56 @@
         return biometricManager.getAuthenticatorIds(userId);
     }
 
+    // Returns whether the device can become unlocked for the specified user via one of that user's
+    // non-strong biometrics or trust agents.  This assumes that the device is currently locked, or
+    // is becoming locked, for the user.
+    private boolean isWeakUnlockMethodEnabled(int userId) {
+
+        // Check whether the system currently allows the use of non-strong biometrics for the user,
+        // *and* the user actually has a non-strong biometric enrolled.
+        //
+        // The biometrics framework ostensibly supports multiple sensors per modality.  However,
+        // that feature is unused and untested.  So, we simply consider one sensor per modality.
+        //
+        // Also, currently we just consider fingerprint and face, matching Keyguard.  If Keyguard
+        // starts supporting other biometric modalities, this will need to be updated.
+        if (mStrongAuthTracker.isBiometricAllowedForUser(/* isStrongBiometric= */ false, userId)) {
+            DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
+            int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userId);
+
+            if (mFingerprintManager != null
+                    && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) == 0
+                    && mFingerprintManager.hasEnrolledTemplates(userId)
+                    && isWeakOrConvenienceSensor(
+                            mFingerprintManager.getSensorProperties().get(0))) {
+                Slog.i(TAG, "User is unlockable by non-strong fingerprint auth");
+                return true;
+            }
+
+            if (mFaceManager != null
+                    && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FACE) == 0
+                    && mFaceManager.hasEnrolledTemplates(userId)
+                    && isWeakOrConvenienceSensor(mFaceManager.getSensorProperties().get(0))) {
+                Slog.i(TAG, "User is unlockable by non-strong face auth");
+                return true;
+            }
+        }
+
+        // Check whether it's possible for the device to be actively unlocked by a trust agent.
+        if (getUserTrustStateInner(userId) == TrustState.TRUSTABLE
+                || (isAutomotive() && isTrustUsuallyManagedInternal(userId))) {
+            Slog.i(TAG, "User is unlockable by trust agent");
+            return true;
+        }
+
+        return false;
+    }
+
+    private static boolean isWeakOrConvenienceSensor(SensorProperties sensor) {
+        return sensor.getSensorStrength() == SensorProperties.STRENGTH_WEAK
+                || sensor.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE;
+    }
+
     // User lifecycle
 
     @Override
@@ -1807,6 +1877,11 @@
         }
     };
 
+    @VisibleForTesting
+    void waitForIdle() {
+        mHandler.runWithScissors(() -> {}, 0);
+    }
+
     private boolean isTrustUsuallyManagedInternal(int userId) {
         synchronized (mTrustUsuallyManagedForUser) {
             int i = mTrustUsuallyManagedForUser.indexOfKey(userId);
@@ -1927,7 +2002,8 @@
         };
     }
 
-    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+    @VisibleForTesting
+    final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override
         public void onSomePackagesChanged() {
             refreshAgentList(UserHandle.USER_ALL);
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 9213d96..ed04e5f 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -34,6 +34,7 @@
     @NonNull private final Looper mLooper;
     @NonNull private final VcnNetworkProvider mVcnNetworkProvider;
     @NonNull private final FeatureFlags mFeatureFlags;
+    @NonNull private final com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
     private final boolean mIsInTestMode;
 
     public VcnContext(
@@ -48,6 +49,7 @@
 
         // Auto-generated class
         mFeatureFlags = new FeatureFlagsImpl();
+        mCoreNetFeatureFlags = new com.android.net.flags.FeatureFlagsImpl();
     }
 
     @NonNull
@@ -69,11 +71,23 @@
         return mIsInTestMode;
     }
 
+    public boolean isFlagNetworkMetricMonitorEnabled() {
+        return mFeatureFlags.networkMetricMonitor();
+    }
+
+    public boolean isFlagIpSecTransformStateEnabled() {
+        return mCoreNetFeatureFlags.ipsecTransformState();
+    }
+
     @NonNull
     public FeatureFlags getFeatureFlags() {
         return mFeatureFlags;
     }
 
+    public boolean isFlagSafeModeTimeoutConfigEnabled() {
+        return mFeatureFlags.safeModeTimeoutConfig();
+    }
+
     /**
      * Verifies that the caller is running on the VcnContext Thread.
      *
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 54c97dd..3094b18 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -915,9 +915,11 @@
             // TODO(b/180132994): explore safely removing this Thread check
             mVcnContext.ensureRunningOnLooperThread();
 
-            logInfo(
-                    "Selected underlying network changed: "
-                            + (underlying == null ? null : underlying.network));
+            if (!UnderlyingNetworkRecord.isSameNetwork(mUnderlying, underlying)) {
+                logInfo(
+                        "Selected underlying network changed: "
+                                + (underlying == null ? null : underlying.network));
+            }
 
             // TODO(b/179091925): Move the delayed-message handling to BaseState
 
@@ -1242,9 +1244,28 @@
                 createScheduledAlarm(
                         SAFEMODE_TIMEOUT_ALARM,
                         delayedMessage,
-                        mVcnContext.isInTestMode()
-                                ? TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS_TEST_MODE)
-                                : TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+                        getSafeModeTimeoutMs(mVcnContext, mLastSnapshot, mSubscriptionGroup));
+    }
+
+    /** Gets the safe mode timeout */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static long getSafeModeTimeoutMs(
+            VcnContext vcnContext, TelephonySubscriptionSnapshot snapshot, ParcelUuid subGrp) {
+        final int defaultSeconds =
+                vcnContext.isInTestMode()
+                        ? SAFEMODE_TIMEOUT_SECONDS_TEST_MODE
+                        : SAFEMODE_TIMEOUT_SECONDS;
+
+        final PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
+        int resultSeconds = defaultSeconds;
+
+        if (vcnContext.isFlagSafeModeTimeoutConfigEnabled() && carrierConfig != null) {
+            resultSeconds =
+                    carrierConfig.getInt(
+                            VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, defaultSeconds);
+        }
+
+        return TimeUnit.SECONDS.toMillis(resultSeconds);
     }
 
     private void cancelSafeModeAlarm() {
@@ -1889,6 +1910,12 @@
                 // Transforms do not need to be persisted; the IkeSession will keep them alive
                 mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
 
+                if (direction == IpSecManager.DIRECTION_IN
+                        && mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                        && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                    mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
+                }
+
                 // For inbound transforms, additionally allow forwarded traffic to bridge to DUN (as
                 // needed)
                 final Set<Integer> exposedCaps = mConnectionConfig.getAllExposedCapabilities();
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
new file mode 100644
index 0000000..5f4852f
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn.routeselection;
+
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.IpSecTransformState;
+import android.net.Network;
+import android.net.vcn.VcnManager;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.OutcomeReceiver;
+import android.os.PowerManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.VcnContext;
+
+import java.util.BitSet;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * IpSecPacketLossDetector is responsible for continuously monitoring IPsec packet loss
+ *
+ * <p>When the packet loss rate surpass the threshold, IpSecPacketLossDetector will report it to the
+ * caller
+ *
+ * <p>IpSecPacketLossDetector will start monitoring when the network being monitored is selected AND
+ * an inbound IpSecTransform has been applied to this network.
+ *
+ * <p>This class is flag gated by "network_metric_monitor" and "ipsec_tramsform_state"
+ */
+public class IpSecPacketLossDetector extends NetworkMetricMonitor {
+    private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int PACKET_LOSS_UNAVALAIBLE = -1;
+
+    // For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
+    // significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
+    // Security"). For audio and video streaming, above 10-12% packet loss is unacceptable (as per
+    // "ICTP-SDU: About PingER"). Thus choose 12% as a conservative default threshold to declare a
+    // validation failure.
+    private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT = 12;
+
+    private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
+
+    private long mPollIpSecStateIntervalMs;
+    private final int mPacketLossRatePercentThreshold;
+
+    @NonNull private final Handler mHandler;
+    @NonNull private final PowerManager mPowerManager;
+    @NonNull private final Object mCancellationToken = new Object();
+    @NonNull private final PacketLossCalculator mPacketLossCalculator;
+
+    @Nullable private IpSecTransformWrapper mInboundTransform;
+    @Nullable private IpSecTransformState mLastIpSecTransformState;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public IpSecPacketLossDetector(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback,
+            @NonNull Dependencies deps)
+            throws IllegalAccessException {
+        super(vcnContext, network, carrierConfig, callback);
+
+        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);
+
+        mPacketLossCalculator = deps.getPacketLossCalculator();
+
+        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+        mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+
+        // Register for system broadcasts to monitor idle mode change
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        getVcnContext()
+                .getContext()
+                .registerReceiver(
+                        new BroadcastReceiver() {
+                            @Override
+                            public void onReceive(Context context, Intent intent) {
+                                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
+                                                intent.getAction())
+                                        && mPowerManager.isDeviceIdleMode()) {
+                                    mLastIpSecTransformState = null;
+                                }
+                            }
+                        },
+                        intentFilter,
+                        null /* broadcastPermission not required */,
+                        mHandler);
+    }
+
+    public IpSecPacketLossDetector(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback)
+            throws IllegalAccessException {
+        this(vcnContext, network, carrierConfig, callback, new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        public PacketLossCalculator getPacketLossCalculator() {
+            return new PacketLossCalculator();
+        }
+    }
+
+    private static long getPollIpSecStateIntervalMs(
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        final int seconds;
+
+        if (carrierConfig != null) {
+            seconds =
+                    carrierConfig.getInt(
+                            VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
+                            POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT);
+        } else {
+            seconds = POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT;
+        }
+
+        return TimeUnit.SECONDS.toMillis(seconds);
+    }
+
+    private static int getPacketLossRatePercentThreshold(
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        if (carrierConfig != null) {
+            return carrierConfig.getInt(
+                    VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+                    IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT);
+        }
+        return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
+    }
+
+    @Override
+    protected void onSelectedUnderlyingNetworkChanged() {
+        if (!isSelectedUnderlyingNetwork()) {
+            mInboundTransform = null;
+            stop();
+        }
+
+        // No action when the underlying network got selected. Wait for the inbound transform to
+        // start the monitor
+    }
+
+    @Override
+    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inboundTransform) {
+        Objects.requireNonNull(inboundTransform, "inboundTransform is null");
+
+        if (Objects.equals(inboundTransform, mInboundTransform)) {
+            return;
+        }
+
+        if (!isSelectedUnderlyingNetwork()) {
+            logWtf("setInboundTransform called but network not selected");
+            return;
+        }
+
+        // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be
+        // enabled on the last one as a sample
+        mInboundTransform = inboundTransform;
+        start();
+    }
+
+    @Override
+    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
+        // The already scheduled event will not be affected. The followup events will be scheduled
+        // with the new interval
+        mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+    }
+
+    @Override
+    protected void start() {
+        super.start();
+        clearTransformStateAndPollingEvents();
+        mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L);
+    }
+
+    @Override
+    public void stop() {
+        super.stop();
+        clearTransformStateAndPollingEvents();
+    }
+
+    private void clearTransformStateAndPollingEvents() {
+        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+        mLastIpSecTransformState = null;
+    }
+
+    @Override
+    public void close() {
+        super.close();
+
+        if (mInboundTransform != null) {
+            mInboundTransform.close();
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @Nullable
+    public IpSecTransformState getLastTransformState() {
+        return mLastIpSecTransformState;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    @Nullable
+    public IpSecTransformWrapper getInboundTransformInternal() {
+        return mInboundTransform;
+    }
+
+    private class PollIpSecStateRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (!isStarted()) {
+                logWtf("Monitor stopped but PollIpSecStateRunnable not removed from Handler");
+                return;
+            }
+
+            getInboundTransformInternal()
+                    .getIpSecTransformState(
+                            new HandlerExecutor(mHandler), new IpSecTransformStateReceiver());
+
+            // Schedule for next poll
+            mHandler.postDelayed(
+                    new PollIpSecStateRunnable(), mCancellationToken, mPollIpSecStateIntervalMs);
+        }
+    }
+
+    private class IpSecTransformStateReceiver
+            implements OutcomeReceiver<IpSecTransformState, RuntimeException> {
+        @Override
+        public void onResult(@NonNull IpSecTransformState state) {
+            getVcnContext().ensureRunningOnLooperThread();
+
+            if (!isStarted()) {
+                return;
+            }
+
+            onIpSecTransformStateReceived(state);
+        }
+
+        @Override
+        public void onError(@NonNull RuntimeException error) {
+            getVcnContext().ensureRunningOnLooperThread();
+
+            // Nothing we can do here
+            logW("TransformStateReceiver#onError " + error.toString());
+        }
+    }
+
+    private void onIpSecTransformStateReceived(@NonNull IpSecTransformState state) {
+        if (mLastIpSecTransformState == null) {
+            // This is first time to poll the state
+            mLastIpSecTransformState = state;
+            return;
+        }
+
+        final int packetLossRate =
+                mPacketLossCalculator.getPacketLossRatePercentage(
+                        mLastIpSecTransformState, state, getLogPrefix());
+
+        if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) {
+            return;
+        }
+
+        final String logMsg =
+                "packetLossRate: "
+                        + packetLossRate
+                        + "% in the past "
+                        + (state.getTimestamp() - mLastIpSecTransformState.getTimestamp())
+                        + "ms";
+
+        mLastIpSecTransformState = state;
+        if (packetLossRate < mPacketLossRatePercentThreshold) {
+            logV(logMsg);
+            onValidationResultReceivedInternal(false /* isFailed */);
+        } else {
+            logInfo(logMsg);
+            onValidationResultReceivedInternal(true /* isFailed */);
+        }
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class PacketLossCalculator {
+        /** Calculate the packet loss rate between two timestamps */
+        public int getPacketLossRatePercentage(
+                @NonNull IpSecTransformState oldState,
+                @NonNull IpSecTransformState newState,
+                String logPrefix) {
+            logVIpSecTransform("oldState", oldState, logPrefix);
+            logVIpSecTransform("newState", newState, logPrefix);
+
+            final int replayWindowSize = oldState.getReplayBitmap().length * 8;
+            final long oldSeqHi = oldState.getRxHighestSequenceNumber();
+            final long oldSeqLow = Math.max(0L, oldSeqHi - replayWindowSize + 1);
+            final long newSeqHi = newState.getRxHighestSequenceNumber();
+            final long newSeqLow = Math.max(0L, newSeqHi - replayWindowSize + 1);
+
+            if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
+                // The replay window did not proceed and all packets might have been delivered out
+                // of order
+                return PACKET_LOSS_UNAVALAIBLE;
+            }
+
+            // Get the expected packet count by assuming there is no packet loss. In this case, SA
+            // should receive all packets whose sequence numbers are smaller than the lower bound of
+            // the replay window AND the packets received within the window.
+            // When the lower bound is 0, it's not possible to tell whether packet with seqNo 0 is
+            // received or not. For simplicity just assume that packet is received.
+            final long newExpectedPktCnt = newSeqLow + getPacketCntInReplayWindow(newState);
+            final long oldExpectedPktCnt = oldSeqLow + getPacketCntInReplayWindow(oldState);
+
+            final long expectedPktCntDiff = newExpectedPktCnt - oldExpectedPktCnt;
+            final long actualPktCntDiff = newState.getPacketCount() - oldState.getPacketCount();
+
+            logV(
+                    TAG,
+                    logPrefix
+                            + " expectedPktCntDiff: "
+                            + expectedPktCntDiff
+                            + " actualPktCntDiff: "
+                            + actualPktCntDiff);
+
+            if (expectedPktCntDiff < 0
+                    || expectedPktCntDiff == 0
+                    || actualPktCntDiff < 0
+                    || actualPktCntDiff > expectedPktCntDiff) {
+                logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
+                return PACKET_LOSS_UNAVALAIBLE;
+            }
+
+            return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+        }
+    }
+
+    private static void logVIpSecTransform(
+            String transformTag, IpSecTransformState state, String logPrefix) {
+        final String stateString =
+                " seqNo: "
+                        + state.getRxHighestSequenceNumber()
+                        + " | pktCnt: "
+                        + state.getPacketCount()
+                        + " | pktCntInWindow: "
+                        + getPacketCntInReplayWindow(state);
+        logV(TAG, logPrefix + " " + transformTag + stateString);
+    }
+
+    /** Get the number of received packets within the replay window */
+    private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
+        return BitSet.valueOf(state.getReplayBitmap()).cardinality();
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
new file mode 100644
index 0000000..a79f188
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn.routeselection;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpSecTransform;
+import android.net.IpSecTransformState;
+import android.net.Network;
+import android.os.OutcomeReceiver;
+import android.util.CloseGuard;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.VcnContext;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * NetworkMetricMonitor is responsible for managing metric monitoring and tracking validation
+ * results.
+ *
+ * <p>This class is flag gated by "network_metric_monitor"
+ */
+public abstract class NetworkMetricMonitor implements AutoCloseable {
+    private static final String TAG = NetworkMetricMonitor.class.getSimpleName();
+
+    private static final boolean VDBG = false; // STOPSHIP: if true
+
+    @NonNull private final CloseGuard mCloseGuard = new CloseGuard();
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final Network mNetwork;
+    @NonNull private final NetworkMetricMonitorCallback mCallback;
+
+    private boolean mIsSelectedUnderlyingNetwork;
+    private boolean mIsStarted;
+    private boolean mIsValidationFailed;
+
+    protected NetworkMetricMonitor(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkMetricMonitorCallback callback)
+            throws IllegalAccessException {
+        if (!vcnContext.isFlagNetworkMetricMonitorEnabled()) {
+            // Caller error
+            logWtf("networkMetricMonitor flag disabled");
+            throw new IllegalAccessException("networkMetricMonitor flag disabled");
+        }
+
+        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mNetwork = Objects.requireNonNull(network, "Missing network");
+        mCallback = Objects.requireNonNull(callback, "Missing callback");
+
+        mIsSelectedUnderlyingNetwork = false;
+        mIsStarted = false;
+        mIsValidationFailed = false;
+    }
+
+    /** Callback to notify caller of the validation result */
+    public interface NetworkMetricMonitorCallback {
+        /** Called when there is a validation result is ready */
+        void onValidationResultReceived();
+    }
+
+    /**
+     * Start monitoring
+     *
+     * <p>This method might be called on a an already started monitor for updating monitor
+     * properties (e.g. IpSecTransform, carrier config)
+     *
+     * <p>Subclasses MUST call super.start() when overriding this method
+     */
+    protected void start() {
+        mIsStarted = true;
+    }
+
+    /**
+     * Stop monitoring
+     *
+     * <p>Subclasses MUST call super.stop() when overriding this method
+     */
+    public void stop() {
+        mIsValidationFailed = false;
+        mIsStarted = false;
+    }
+
+    /** Called by the subclasses when the validation result is ready */
+    protected void onValidationResultReceivedInternal(boolean isFailed) {
+        mIsValidationFailed = isFailed;
+        mCallback.onValidationResultReceived();
+    }
+
+    /** Called when the underlying network changes to selected or unselected */
+    protected abstract void onSelectedUnderlyingNetworkChanged();
+
+    /**
+     * Mark the network being monitored selected or unselected
+     *
+     * <p>Subclasses MUST call super when overriding this method
+     */
+    public void setIsSelectedUnderlyingNetwork(boolean isSelectedUnderlyingNetwork) {
+        if (mIsSelectedUnderlyingNetwork == isSelectedUnderlyingNetwork) {
+            return;
+        }
+
+        mIsSelectedUnderlyingNetwork = isSelectedUnderlyingNetwork;
+        onSelectedUnderlyingNetworkChanged();
+    }
+
+    /** Wrapper that allows injection for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PROTECTED)
+    public static class IpSecTransformWrapper {
+        @NonNull public final IpSecTransform ipSecTransform;
+
+        public IpSecTransformWrapper(@NonNull IpSecTransform ipSecTransform) {
+            this.ipSecTransform = ipSecTransform;
+        }
+
+        /** Poll an IpSecTransformState */
+        public void getIpSecTransformState(
+                @NonNull Executor executor,
+                @NonNull OutcomeReceiver<IpSecTransformState, RuntimeException> callback) {
+            ipSecTransform.getIpSecTransformState(executor, callback);
+        }
+
+        /** Close this instance and release the underlying resources */
+        public void close() {
+            ipSecTransform.close();
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(ipSecTransform);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof IpSecTransformWrapper)) {
+                return false;
+            }
+
+            final IpSecTransformWrapper other = (IpSecTransformWrapper) o;
+
+            return Objects.equals(ipSecTransform, other.ipSecTransform);
+        }
+    }
+
+    /** Set the IpSecTransform that applied to the Network being monitored */
+    public void setInboundTransform(@NonNull IpSecTransform inTransform) {
+        setInboundTransformInternal(new IpSecTransformWrapper(inTransform));
+    }
+
+    /**
+     * Set the IpSecTransform that applied to the Network being monitored *
+     *
+     * <p>Subclasses MUST call super when overriding this method
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public void setInboundTransformInternal(@NonNull IpSecTransformWrapper inTransform) {
+        // Subclasses MUST override it if they care
+    }
+
+    /** Update the carrierconfig */
+    public void setCarrierConfig(@Nullable PersistableBundleWrapper carrierConfig) {
+        // Subclasses MUST override it if they care
+    }
+
+    public boolean isValidationFailed() {
+        return mIsValidationFailed;
+    }
+
+    public boolean isSelectedUnderlyingNetwork() {
+        return mIsSelectedUnderlyingNetwork;
+    }
+
+    public boolean isStarted() {
+        return mIsStarted;
+    }
+
+    @NonNull
+    public VcnContext getVcnContext() {
+        return mVcnContext;
+    }
+
+    // Override methods for AutoCloseable. Subclasses MUST call super when overriding this method
+    @Override
+    public void close() {
+        mCloseGuard.close();
+
+        stop();
+    }
+
+    // Override #finalize() to use closeGuard for flagging that #close() was not called
+    @SuppressWarnings("Finalize")
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private String getClassName() {
+        return this.getClass().getSimpleName();
+    }
+
+    protected String getLogPrefix() {
+        return " [Network " + mNetwork + "] ";
+    }
+
+    protected void logV(String msg) {
+        if (VDBG) {
+            Slog.v(getClassName(), getLogPrefix() + msg);
+            LOCAL_LOG.log("[VERBOSE ] " + getClassName() + getLogPrefix() + msg);
+        }
+    }
+
+    protected void logInfo(String msg) {
+        Slog.i(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected void logW(String msg) {
+        Slog.w(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[WARN ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected void logWtf(String msg) {
+        Slog.wtf(getClassName(), getLogPrefix() + msg);
+        LOCAL_LOG.log("[WTF ] " + getClassName() + getLogPrefix() + msg);
+    }
+
+    protected static void logV(String className, String msgWithPrefix) {
+        if (VDBG) {
+            Slog.wtf(className, msgWithPrefix);
+            LOCAL_LOG.log("[VERBOSE ] " + className + msgWithPrefix);
+        }
+    }
+
+    protected static void logWtf(String className, String msgWithPrefix) {
+        Slog.wtf(className, msgWithPrefix);
+        LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index 7f129ea..d32e5cc 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -47,7 +47,6 @@
 
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 
 /** @hide */
@@ -86,7 +85,6 @@
      * <p>VCN MUST never select a non-INTERNET network that are unvalidated or fail to match any
      * template as the underlying network.
      */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int PRIORITY_INVALID = -1;
 
     /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
@@ -96,7 +94,7 @@
             List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             PersistableBundleWrapper carrierConfig) {
         // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
 
@@ -118,7 +116,7 @@
                     networkRecord,
                     subscriptionGroup,
                     snapshot,
-                    currentlySelected,
+                    isSelected,
                     carrierConfig)) {
                 return priorityIndex;
             }
@@ -140,12 +138,9 @@
             UnderlyingNetworkRecord networkRecord,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             PersistableBundleWrapper carrierConfig) {
         final NetworkCapabilities caps = networkRecord.networkCapabilities;
-        final boolean isSelectedUnderlyingNetwork =
-                currentlySelected != null
-                        && Objects.equals(currentlySelected.network, networkRecord.network);
 
         final int meteredMatch = networkPriority.getMetered();
         final boolean isMetered = !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
@@ -159,7 +154,7 @@
         if (caps.getLinkUpstreamBandwidthKbps() < networkPriority.getMinExitUpstreamBandwidthKbps()
                 || (caps.getLinkUpstreamBandwidthKbps()
                                 < networkPriority.getMinEntryUpstreamBandwidthKbps()
-                        && !isSelectedUnderlyingNetwork)) {
+                        && !isSelected)) {
             return false;
         }
 
@@ -167,7 +162,7 @@
                         < networkPriority.getMinExitDownstreamBandwidthKbps()
                 || (caps.getLinkDownstreamBandwidthKbps()
                                 < networkPriority.getMinEntryDownstreamBandwidthKbps()
-                        && !isSelectedUnderlyingNetwork)) {
+                        && !isSelected)) {
             return false;
         }
 
@@ -191,7 +186,7 @@
             return checkMatchesWifiPriorityRule(
                     (VcnWifiUnderlyingNetworkTemplate) networkPriority,
                     networkRecord,
-                    currentlySelected,
+                    isSelected,
                     carrierConfig);
         }
 
@@ -214,7 +209,7 @@
     public static boolean checkMatchesWifiPriorityRule(
             VcnWifiUnderlyingNetworkTemplate networkPriority,
             UnderlyingNetworkRecord networkRecord,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             PersistableBundleWrapper carrierConfig) {
         final NetworkCapabilities caps = networkRecord.networkCapabilities;
 
@@ -223,7 +218,7 @@
         }
 
         // TODO: Move the Network Quality check to the network metric monitor framework.
-        if (!isWifiRssiAcceptable(networkRecord, currentlySelected, carrierConfig)) {
+        if (!isWifiRssiAcceptable(networkRecord, isSelected, carrierConfig)) {
             return false;
         }
 
@@ -237,15 +232,11 @@
 
     private static boolean isWifiRssiAcceptable(
             UnderlyingNetworkRecord networkRecord,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             PersistableBundleWrapper carrierConfig) {
         final NetworkCapabilities caps = networkRecord.networkCapabilities;
-        final boolean isSelectedNetwork =
-                currentlySelected != null
-                        && networkRecord.network.equals(currentlySelected.network);
 
-        if (isSelectedNetwork
-                && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
+        if (isSelected && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
             return true;
         }
 
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 6afa795..3f8d39e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -30,6 +30,7 @@
 import android.annotation.Nullable;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpSecTransform;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -48,9 +49,11 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
 import com.android.server.vcn.util.LogUtils;
 
 import java.util.ArrayList;
@@ -83,6 +86,9 @@
     @NonNull private final TelephonyCallback mActiveDataSubIdListener =
             new VcnActiveDataSubscriptionIdListener();
 
+    private final Map<Network, UnderlyingNetworkEvaluator> mUnderlyingNetworkRecords =
+            new ArrayMap<>();
+
     @NonNull private final List<NetworkCallback> mCellBringupCallbacks = new ArrayList<>();
     @Nullable private NetworkCallback mWifiBringupCallback;
     @Nullable private NetworkCallback mWifiEntryRssiThresholdCallback;
@@ -105,7 +111,8 @@
         this(vcnContext, connectionConfig, subscriptionGroup, snapshot, cb, new Dependencies());
     }
 
-    private UnderlyingNetworkController(
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    UnderlyingNetworkController(
             @NonNull VcnContext vcnContext,
             @NonNull VcnGatewayConnectionConfig connectionConfig,
             @NonNull ParcelUuid subscriptionGroup,
@@ -197,6 +204,15 @@
         List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
         mCellBringupCallbacks.clear();
 
+        if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+            for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+                evaluator.close();
+            }
+        }
+
+        mUnderlyingNetworkRecords.clear();
+
         // Register new callbacks. Make-before-break; always register new callbacks before removal
         // of old callbacks
         if (!mIsQuitting) {
@@ -395,15 +411,58 @@
         // Update carrier config
         mCarrierConfig = mLastSnapshot.getCarrierConfigForSubGrp(mSubscriptionGroup);
 
+        // Make sure all evaluators use the same updated TelephonySubscriptionSnapshot and carrier
+        // config to calculate their cached priority classes. For simplicity, the
+        // UnderlyingNetworkController does not listen for changes in VCN-related carrier config
+        // keys, and changes are applied at restart of the VcnGatewayConnection
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            evaluator.reevaluate(
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+        }
+
         // Only trigger re-registration if subIds in this group have changed
         if (oldSnapshot
                 .getAllSubIdsInGroup(mSubscriptionGroup)
                 .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
+
+            if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                    && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                reevaluateNetworks();
+            }
             return;
         }
         registerOrUpdateNetworkRequests();
     }
 
+    /**
+     * Pass the IpSecTransform of the VCN to UnderlyingNetworkController for metric monitoring
+     *
+     * <p>Caller MUST call it when IpSecTransforms have been created for VCN creation or migration
+     */
+    public void updateInboundTransform(
+            @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
+        if (!mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                || !mVcnContext.isFlagIpSecTransformStateEnabled()) {
+            logWtf("#updateInboundTransform: unexpected call; flags missing");
+            return;
+        }
+
+        Objects.requireNonNull(currentNetwork, "currentNetwork is null");
+        Objects.requireNonNull(transform, "transform is null");
+
+        if (mCurrentRecord == null
+                || mRouteSelectionCallback == null
+                || !Objects.equals(currentNetwork.network, mCurrentRecord.network)) {
+            // The caller (VcnGatewayConnection) is out-of-dated. Ignore this call.
+            return;
+        }
+
+        mUnderlyingNetworkRecords.get(mCurrentRecord.network).setInboundTransform(transform);
+    }
+
     /** Tears down this Tracker, and releases all underlying network requests. */
     public void teardown() {
         mVcnContext.ensureRunningOnLooperThread();
@@ -418,32 +477,62 @@
                 .unregisterTelephonyCallback(mActiveDataSubIdListener);
     }
 
+    private TreeSet<UnderlyingNetworkEvaluator> getSortedUnderlyingNetworks() {
+        TreeSet<UnderlyingNetworkEvaluator> sorted =
+                new TreeSet<>(UnderlyingNetworkEvaluator.getComparator(mVcnContext));
+
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            if (evaluator.getPriorityClass() != NetworkPriorityClassifier.PRIORITY_INVALID) {
+                sorted.add(evaluator);
+            }
+        }
+
+        return sorted;
+    }
+
     private void reevaluateNetworks() {
         if (mIsQuitting || mRouteSelectionCallback == null) {
             return; // UnderlyingNetworkController has quit.
         }
 
-        TreeSet<UnderlyingNetworkRecord> sorted =
-                mRouteSelectionCallback.getSortedUnderlyingNetworks();
-        UnderlyingNetworkRecord candidate = sorted.isEmpty() ? null : sorted.first();
+        TreeSet<UnderlyingNetworkEvaluator> sorted = getSortedUnderlyingNetworks();
+
+        UnderlyingNetworkEvaluator candidateEvaluator = sorted.isEmpty() ? null : sorted.first();
+        UnderlyingNetworkRecord candidate =
+                candidateEvaluator == null ? null : candidateEvaluator.getNetworkRecord();
         if (Objects.equals(mCurrentRecord, candidate)) {
             return;
         }
 
         String allNetworkPriorities = "";
-        for (UnderlyingNetworkRecord record : sorted) {
+        for (UnderlyingNetworkEvaluator recordEvaluator : sorted) {
             if (!allNetworkPriorities.isEmpty()) {
                 allNetworkPriorities += ", ";
             }
-            allNetworkPriorities += record.network + ": " + record.priorityClass;
+            allNetworkPriorities +=
+                    recordEvaluator.getNetwork() + ": " + recordEvaluator.getPriorityClass();
         }
-        logInfo(
-                "Selected network changed to "
-                        + (candidate == null ? null : candidate.network)
-                        + ", selected from list: "
-                        + allNetworkPriorities);
+
+        if (!UnderlyingNetworkRecord.isSameNetwork(mCurrentRecord, candidate)) {
+            logInfo(
+                    "Selected network changed to "
+                            + (candidate == null ? null : candidate.network)
+                            + ", selected from list: "
+                            + allNetworkPriorities);
+        }
+
         mCurrentRecord = candidate;
         mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
+
+        // Need to update all evaluators to ensure the previously selected one is unselected
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            evaluator.setIsSelected(
+                    candidateEvaluator == evaluator,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+        }
     }
 
     /**
@@ -463,46 +552,32 @@
      */
     @VisibleForTesting
     class UnderlyingNetworkListener extends NetworkCallback {
-        private final Map<Network, UnderlyingNetworkRecord.Builder>
-                mUnderlyingNetworkRecordBuilders = new ArrayMap<>();
-
         UnderlyingNetworkListener() {
             super(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO);
         }
 
-        private TreeSet<UnderlyingNetworkRecord> getSortedUnderlyingNetworks() {
-            TreeSet<UnderlyingNetworkRecord> sorted =
-                    new TreeSet<>(UnderlyingNetworkRecord.getComparator());
-
-            for (UnderlyingNetworkRecord.Builder builder :
-                    mUnderlyingNetworkRecordBuilders.values()) {
-                if (builder.isValid()) {
-                    final UnderlyingNetworkRecord record =
-                            builder.build(
-                                    mVcnContext,
-                                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
-                                    mSubscriptionGroup,
-                                    mLastSnapshot,
-                                    mCurrentRecord,
-                                    mCarrierConfig);
-                    if (record.priorityClass != NetworkPriorityClassifier.PRIORITY_INVALID) {
-                        sorted.add(record);
-                    }
-                }
-            }
-
-            return sorted;
-        }
-
         @Override
         public void onAvailable(@NonNull Network network) {
-            mUnderlyingNetworkRecordBuilders.put(
-                    network, new UnderlyingNetworkRecord.Builder(network));
+            mUnderlyingNetworkRecords.put(
+                    network,
+                    mDeps.newUnderlyingNetworkEvaluator(
+                            mVcnContext,
+                            network,
+                            mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                            mSubscriptionGroup,
+                            mLastSnapshot,
+                            mCarrierConfig,
+                            new NetworkEvaluatorCallbackImpl()));
         }
 
         @Override
         public void onLost(@NonNull Network network) {
-            mUnderlyingNetworkRecordBuilders.remove(network);
+            if (mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                    && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                mUnderlyingNetworkRecords.get(network).close();
+            }
+
+            mUnderlyingNetworkRecords.remove(network);
 
             reevaluateNetworks();
         }
@@ -510,15 +585,20 @@
         @Override
         public void onCapabilitiesChanged(
                 @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
-            final UnderlyingNetworkRecord.Builder builder =
-                    mUnderlyingNetworkRecordBuilders.get(network);
-            if (builder == null) {
+            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+            if (evaluator == null) {
                 logWtf("Got capabilities change for unknown key: " + network);
                 return;
             }
 
-            builder.setNetworkCapabilities(networkCapabilities);
-            if (builder.isValid()) {
+            evaluator.setNetworkCapabilities(
+                    networkCapabilities,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+
+            if (evaluator.isValid()) {
                 reevaluateNetworks();
             }
         }
@@ -526,35 +606,60 @@
         @Override
         public void onLinkPropertiesChanged(
                 @NonNull Network network, @NonNull LinkProperties linkProperties) {
-            final UnderlyingNetworkRecord.Builder builder =
-                    mUnderlyingNetworkRecordBuilders.get(network);
-            if (builder == null) {
+            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+            if (evaluator == null) {
                 logWtf("Got link properties change for unknown key: " + network);
                 return;
             }
 
-            builder.setLinkProperties(linkProperties);
-            if (builder.isValid()) {
+            evaluator.setLinkProperties(
+                    linkProperties,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+
+            if (evaluator.isValid()) {
                 reevaluateNetworks();
             }
         }
 
         @Override
         public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
-            final UnderlyingNetworkRecord.Builder builder =
-                    mUnderlyingNetworkRecordBuilders.get(network);
-            if (builder == null) {
+            final UnderlyingNetworkEvaluator evaluator = mUnderlyingNetworkRecords.get(network);
+            if (evaluator == null) {
                 logWtf("Got blocked status change for unknown key: " + network);
                 return;
             }
 
-            builder.setIsBlocked(isBlocked);
-            if (builder.isValid()) {
+            evaluator.setIsBlocked(
+                    isBlocked,
+                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                    mSubscriptionGroup,
+                    mLastSnapshot,
+                    mCarrierConfig);
+
+            if (evaluator.isValid()) {
                 reevaluateNetworks();
             }
         }
     }
 
+    @VisibleForTesting
+    class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
+        @Override
+        public void onEvaluationResultChanged() {
+            if (!mVcnContext.isFlagNetworkMetricMonitorEnabled()
+                    || !mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                logWtf("#onEvaluationResultChanged: unexpected call; flags missing");
+                return;
+            }
+
+            mVcnContext.ensureRunningOnLooperThread();
+            reevaluateNetworks();
+        }
+    }
+
     private String getLogPrefix() {
         return "("
                 + LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
@@ -614,16 +719,8 @@
         pw.println("Underlying networks:");
         pw.increaseIndent();
         if (mRouteSelectionCallback != null) {
-            for (UnderlyingNetworkRecord record :
-                    mRouteSelectionCallback.getSortedUnderlyingNetworks()) {
-                record.dump(
-                        mVcnContext,
-                        pw,
-                        mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
-                        mSubscriptionGroup,
-                        mLastSnapshot,
-                        mCurrentRecord,
-                        mCarrierConfig);
+            for (UnderlyingNetworkEvaluator recordEvaluator : getSortedUnderlyingNetworks()) {
+                recordEvaluator.dump(pw);
             }
         }
         pw.decreaseIndent();
@@ -653,5 +750,24 @@
                 @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
     }
 
-    private static class Dependencies {}
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        public UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator(
+                @NonNull VcnContext vcnContext,
+                @NonNull Network network,
+                @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+                @NonNull ParcelUuid subscriptionGroup,
+                @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+                @Nullable PersistableBundleWrapper carrierConfig,
+                @NonNull NetworkEvaluatorCallback evaluatorCallback) {
+            return new UnderlyingNetworkEvaluator(
+                    vcnContext,
+                    network,
+                    underlyingNetworkTemplates,
+                    subscriptionGroup,
+                    lastSnapshot,
+                    carrierConfig,
+                    evaluatorCallback);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
new file mode 100644
index 0000000..2f4cf5e
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn.routeselection;
+
+import static com.android.server.VcnManagementService.LOCAL_LOG;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpSecTransform;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * UnderlyingNetworkEvaluator evaluates the quality and priority class of a network candidate for
+ * route selection.
+ *
+ * @hide
+ */
+public class UnderlyingNetworkEvaluator {
+    private static final String TAG = UnderlyingNetworkEvaluator.class.getSimpleName();
+
+    private static final int[] PENALTY_TIMEOUT_MINUTES_DEFAULT = new int[] {5};
+
+    @NonNull private final VcnContext mVcnContext;
+    @NonNull private final Handler mHandler;
+    @NonNull private final Object mCancellationToken = new Object();
+
+    @NonNull private final UnderlyingNetworkRecord.Builder mNetworkRecordBuilder;
+
+    @NonNull private final NetworkEvaluatorCallback mEvaluatorCallback;
+    @NonNull private final List<NetworkMetricMonitor> mMetricMonitors = new ArrayList<>();
+
+    @NonNull private final Dependencies mDependencies;
+
+    // TODO: Support back-off timeouts
+    private long mPenalizedTimeoutMs;
+
+    private boolean mIsSelected;
+    private boolean mIsPenalized;
+    private int mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public UnderlyingNetworkEvaluator(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkEvaluatorCallback evaluatorCallback,
+            @NonNull Dependencies dependencies) {
+        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mHandler = new Handler(mVcnContext.getLooper());
+
+        mDependencies = Objects.requireNonNull(dependencies, "Missing dependencies");
+        mEvaluatorCallback = Objects.requireNonNull(evaluatorCallback, "Missing deps");
+
+        Objects.requireNonNull(underlyingNetworkTemplates, "Missing underlyingNetworkTemplates");
+        Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        Objects.requireNonNull(lastSnapshot, "Missing lastSnapshot");
+
+        mNetworkRecordBuilder =
+                new UnderlyingNetworkRecord.Builder(
+                        Objects.requireNonNull(network, "Missing network"));
+        mIsSelected = false;
+        mIsPenalized = false;
+        mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
+
+        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
+            }
+        }
+    }
+
+    public UnderlyingNetworkEvaluator(
+            @NonNull VcnContext vcnContext,
+            @NonNull Network network,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig,
+            @NonNull NetworkEvaluatorCallback evaluatorCallback) {
+        this(
+                vcnContext,
+                network,
+                underlyingNetworkTemplates,
+                subscriptionGroup,
+                lastSnapshot,
+                carrierConfig,
+                evaluatorCallback,
+                new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Get an IpSecPacketLossDetector instance */
+        public IpSecPacketLossDetector newIpSecPacketLossDetector(
+                @NonNull VcnContext vcnContext,
+                @NonNull Network network,
+                @Nullable PersistableBundleWrapper carrierConfig,
+                @NonNull NetworkMetricMonitor.NetworkMetricMonitorCallback callback)
+                throws IllegalAccessException {
+            return new IpSecPacketLossDetector(vcnContext, network, carrierConfig, callback);
+        }
+    }
+
+    /** Callback to notify caller to reevaluate network selection */
+    public interface NetworkEvaluatorCallback {
+        /**
+         * Called when mIsPenalized changed
+         *
+         * <p>When receiving this call, UnderlyingNetworkController should reevaluate all network
+         * candidates for VCN underlying network selection
+         */
+        void onEvaluationResultChanged();
+    }
+
+    private class MetricMonitorCallbackImpl
+            implements NetworkMetricMonitor.NetworkMetricMonitorCallback {
+        public void onValidationResultReceived() {
+            mVcnContext.ensureRunningOnLooperThread();
+
+            handleValidationResult();
+        }
+    }
+
+    private void updatePriorityClass(
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        if (mNetworkRecordBuilder.isValid()) {
+            mPriorityClass =
+                    NetworkPriorityClassifier.calculatePriorityClass(
+                            mVcnContext,
+                            mNetworkRecordBuilder.build(),
+                            underlyingNetworkTemplates,
+                            subscriptionGroup,
+                            lastSnapshot,
+                            mIsSelected,
+                            carrierConfig);
+        } else {
+            mPriorityClass = NetworkPriorityClassifier.PRIORITY_INVALID;
+        }
+    }
+
+    private boolean isIpSecPacketLossDetectorEnabled() {
+        return isIpSecPacketLossDetectorEnabled(mVcnContext);
+    }
+
+    private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) {
+        return vcnContext.isFlagIpSecTransformStateEnabled()
+                && vcnContext.isFlagNetworkMetricMonitorEnabled();
+    }
+
+    /** 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;
+                }
+            }
+
+            final int leftIndex = left.mPriorityClass;
+            final int rightIndex = right.mPriorityClass;
+
+            // In the case of networks in the same priority class, prioritize based on other
+            // criteria (eg. actively selected network, link metrics, etc)
+            if (leftIndex == rightIndex) {
+                // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
+                // fall into the same priority class.
+                if (left.mIsSelected) {
+                    return -1;
+                }
+                if (right.mIsSelected) {
+                    return 1;
+                }
+            }
+            return Integer.compare(leftIndex, rightIndex);
+        };
+    }
+
+    private static long getPenaltyTimeoutMs(@Nullable PersistableBundleWrapper carrierConfig) {
+        final int[] timeoutMinuteList;
+
+        if (carrierConfig != null) {
+            timeoutMinuteList =
+                    carrierConfig.getIntArray(
+                            VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
+                            PENALTY_TIMEOUT_MINUTES_DEFAULT);
+        } else {
+            timeoutMinuteList = PENALTY_TIMEOUT_MINUTES_DEFAULT;
+        }
+
+        // TODO: Add the support of back-off timeouts and return the full list
+        return TimeUnit.MINUTES.toMillis(timeoutMinuteList[0]);
+    }
+
+    private void handleValidationResult() {
+        final boolean wasPenalized = mIsPenalized;
+        mIsPenalized = false;
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            mIsPenalized |= monitor.isValidationFailed();
+        }
+
+        if (wasPenalized == mIsPenalized) {
+            return;
+        }
+
+        logInfo(
+                "#handleValidationResult: wasPenalized "
+                        + wasPenalized
+                        + " mIsPenalized "
+                        + mIsPenalized);
+
+        if (mIsPenalized) {
+            mHandler.postDelayed(
+                    new ExitPenaltyBoxRunnable(), mCancellationToken, mPenalizedTimeoutMs);
+        } else {
+            // Exit the penalty box
+            mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+        }
+        mEvaluatorCallback.onEvaluationResultChanged();
+    }
+
+    public class ExitPenaltyBoxRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (!mIsPenalized) {
+                logWtf("Evaluator not being penalized but ExitPenaltyBoxRunnable was scheduled");
+                return;
+            }
+
+            // TODO: There might be a future metric monitor (e.g. ping) that will require the
+            // validation to pass before exiting the penalty box.
+            mIsPenalized = false;
+            mEvaluatorCallback.onEvaluationResultChanged();
+        }
+    }
+
+    /** Set the NetworkCapabilities */
+    public void setNetworkCapabilities(
+            @NonNull NetworkCapabilities nc,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mNetworkRecordBuilder.setNetworkCapabilities(nc);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+    }
+
+    /** Set the LinkProperties */
+    public void setLinkProperties(
+            @NonNull LinkProperties lp,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mNetworkRecordBuilder.setLinkProperties(lp);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+    }
+
+    /** Set whether the network is blocked */
+    public void setIsBlocked(
+            boolean isBlocked,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mNetworkRecordBuilder.setIsBlocked(isBlocked);
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+    }
+
+    /** Set whether the network is selected as VCN's underlying network */
+    public void setIsSelected(
+            boolean isSelected,
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        mIsSelected = isSelected;
+
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setIsSelectedUnderlyingNetwork(isSelected);
+        }
+    }
+
+    /**
+     * Update the last TelephonySubscriptionSnapshot and carrier config to reevaluate the network
+     */
+    public void reevaluate(
+            @NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
+            @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot lastSnapshot,
+            @Nullable PersistableBundleWrapper carrierConfig) {
+        updatePriorityClass(
+                underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
+
+        // The already scheduled event will not be affected. The followup events will be scheduled
+        // with the new timeout
+        mPenalizedTimeoutMs = getPenaltyTimeoutMs(carrierConfig);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setCarrierConfig(carrierConfig);
+        }
+    }
+
+    /** Update the inbound IpSecTransform applied to the network */
+    public void setInboundTransform(@NonNull IpSecTransform transform) {
+        if (!mIsSelected) {
+            logWtf("setInboundTransform on an unselected evaluator");
+            return;
+        }
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.setInboundTransform(transform);
+        }
+    }
+
+    /** Close the evaluator and stop all the underlying network metric monitors */
+    public void close() {
+        mHandler.removeCallbacksAndEqualMessages(mCancellationToken);
+
+        for (NetworkMetricMonitor monitor : mMetricMonitors) {
+            monitor.close();
+        }
+    }
+
+    /** Return whether this network evaluator is valid */
+    public boolean isValid() {
+        return mNetworkRecordBuilder.isValid();
+    }
+
+    /** Return the network */
+    public Network getNetwork() {
+        return mNetworkRecordBuilder.getNetwork();
+    }
+
+    /** Return the network record */
+    public UnderlyingNetworkRecord getNetworkRecord() {
+        return mNetworkRecordBuilder.build();
+    }
+
+    /** Return the priority class for network selection */
+    public int getPriorityClass() {
+        return mPriorityClass;
+    }
+
+    /** Return whether the network is being penalized */
+    public boolean isPenalized() {
+        return mIsPenalized;
+    }
+
+    /** Dump the information of this instance */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("UnderlyingNetworkEvaluator:");
+        pw.increaseIndent();
+
+        if (mNetworkRecordBuilder.isValid()) {
+            getNetworkRecord().dump(pw);
+        } else {
+            pw.println(
+                    "UnderlyingNetworkRecord incomplete: mNetwork: "
+                            + mNetworkRecordBuilder.getNetwork());
+        }
+
+        pw.println("mIsSelected: " + mIsSelected);
+        pw.println("mPriorityClass: " + mPriorityClass);
+        pw.println("mIsPenalized: " + mIsPenalized);
+
+        pw.decreaseIndent();
+    }
+
+    private String getLogPrefix() {
+        return "[Network " + mNetworkRecordBuilder.getNetwork() + "] ";
+    }
+
+    private void logInfo(String msg) {
+        Slog.i(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[INFO ] " + TAG + getLogPrefix() + msg);
+    }
+
+    private void logWtf(String msg) {
+        Slog.wtf(TAG, getLogPrefix() + msg);
+        LOCAL_LOG.log("[WTF ] " + TAG + getLogPrefix() + msg);
+    }
+}
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index aea9f4d..7ab8e55 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -16,24 +16,17 @@
 
 package com.android.server.vcn.routeselection;
 
-import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.vcn.VcnUnderlyingNetworkTemplate;
-import android.os.ParcelUuid;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.VcnContext;
 
-import java.util.Comparator;
-import java.util.List;
 import java.util.Objects;
 
 /**
@@ -46,54 +39,17 @@
     @NonNull public final NetworkCapabilities networkCapabilities;
     @NonNull public final LinkProperties linkProperties;
     public final boolean isBlocked;
-    public final boolean isSelected;
-    public final int priorityClass;
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public UnderlyingNetworkRecord(
             @NonNull Network network,
             @NonNull NetworkCapabilities networkCapabilities,
             @NonNull LinkProperties linkProperties,
-            boolean isBlocked,
-            VcnContext vcnContext,
-            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            ParcelUuid subscriptionGroup,
-            TelephonySubscriptionSnapshot snapshot,
-            UnderlyingNetworkRecord currentlySelected,
-            PersistableBundleWrapper carrierConfig) {
+            boolean isBlocked) {
         this.network = network;
         this.networkCapabilities = networkCapabilities;
         this.linkProperties = linkProperties;
         this.isBlocked = isBlocked;
-
-        this.isSelected = isSelected(this.network, currentlySelected);
-
-        priorityClass =
-                NetworkPriorityClassifier.calculatePriorityClass(
-                        vcnContext,
-                        this,
-                        underlyingNetworkTemplates,
-                        subscriptionGroup,
-                        snapshot,
-                        currentlySelected,
-                        carrierConfig);
-    }
-
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public UnderlyingNetworkRecord(
-            @NonNull Network network,
-            @NonNull NetworkCapabilities networkCapabilities,
-            @NonNull LinkProperties linkProperties,
-            boolean isBlocked,
-            boolean isSelected,
-            int priorityClass) {
-        this.network = network;
-        this.networkCapabilities = networkCapabilities;
-        this.linkProperties = linkProperties;
-        this.isBlocked = isBlocked;
-        this.isSelected = isSelected;
-
-        this.priorityClass = priorityClass;
     }
 
     @Override
@@ -113,64 +69,20 @@
         return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
     }
 
-    /** Returns if two records are equal including their priority classes. */
-    public static boolean isEqualIncludingPriorities(
-            UnderlyingNetworkRecord left, UnderlyingNetworkRecord right) {
-        if (left != null && right != null) {
-            return left.equals(right)
-                    && left.isSelected == right.isSelected
-                    && left.priorityClass == right.priorityClass;
-        }
-
-        return left == right;
-    }
-
-    static Comparator<UnderlyingNetworkRecord> getComparator() {
-        return (left, right) -> {
-            final int leftIndex = left.priorityClass;
-            final int rightIndex = right.priorityClass;
-
-            // In the case of networks in the same priority class, prioritize based on other
-            // criteria (eg. actively selected network, link metrics, etc)
-            if (leftIndex == rightIndex) {
-                // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
-                // fall into the same priority class.
-                if (left.isSelected) {
-                    return -1;
-                }
-                if (right.isSelected) {
-                    return 1;
-                }
-            }
-            return Integer.compare(leftIndex, rightIndex);
-        };
-    }
-
-    private static boolean isSelected(
-            Network networkToCheck, UnderlyingNetworkRecord currentlySelected) {
-        if (currentlySelected == null) {
-            return false;
-        }
-        if (currentlySelected.network.equals(networkToCheck)) {
-            return true;
-        }
-        return false;
+    /** Return whether two records represent the same network */
+    public static boolean isSameNetwork(
+            @Nullable UnderlyingNetworkRecord leftRecord,
+            @Nullable UnderlyingNetworkRecord rightRecord) {
+        final Network left = leftRecord == null ? null : leftRecord.network;
+        final Network right = rightRecord == null ? null : rightRecord.network;
+        return Objects.equals(left, right);
     }
 
     /** Dumps the state of this record for logging and debugging purposes. */
-    void dump(
-            VcnContext vcnContext,
-            IndentingPrintWriter pw,
-            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            ParcelUuid subscriptionGroup,
-            TelephonySubscriptionSnapshot snapshot,
-            UnderlyingNetworkRecord currentlySelected,
-            PersistableBundleWrapper carrierConfig) {
+    void dump(IndentingPrintWriter pw) {
         pw.println("UnderlyingNetworkRecord:");
         pw.increaseIndent();
 
-        pw.println("priorityClass: " + priorityClass);
-        pw.println("isSelected: " + isSelected);
         pw.println("mNetwork: " + network);
         pw.println("mNetworkCapabilities: " + networkCapabilities);
         pw.println("mLinkProperties: " + linkProperties);
@@ -218,29 +130,14 @@
             return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
         }
 
-        UnderlyingNetworkRecord build(
-                VcnContext vcnContext,
-                List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-                ParcelUuid subscriptionGroup,
-                TelephonySubscriptionSnapshot snapshot,
-                UnderlyingNetworkRecord currentlySelected,
-                PersistableBundleWrapper carrierConfig) {
+        UnderlyingNetworkRecord build() {
             if (!isValid()) {
                 throw new IllegalArgumentException(
                         "Called build before UnderlyingNetworkRecord was valid");
             }
 
             return new UnderlyingNetworkRecord(
-                    mNetwork,
-                    mNetworkCapabilities,
-                    mLinkProperties,
-                    mIsBlocked,
-                    vcnContext,
-                    underlyingNetworkTemplates,
-                    subscriptionGroup,
-                    snapshot,
-                    currentlySelected,
-                    carrierConfig);
+                    mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wearable/OWNERS b/services/core/java/com/android/server/wearable/OWNERS
index 073e2d7..eca48b7 100644
--- a/services/core/java/com/android/server/wearable/OWNERS
+++ b/services/core/java/com/android/server/wearable/OWNERS
@@ -1,3 +1 @@
-charliewang@google.com
-oni@google.com
-volnov@google.com
\ No newline at end of file
+include /core/java/android/app/wearable/OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index cd70447..e06f215 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -17,6 +17,7 @@
 rgl@google.com
 yunfanc@google.com
 wilsonshih@google.com
+jiamingliu@google.com
 
 # Files related to background activity launches
 per-file Background*Start* = set noparent
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e51afbe..0bc60cd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4708,6 +4708,7 @@
         }
         if (top.isAttached()) {
             top.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+            top.mWaitForEnteringPinnedMode = false;
         }
     }
 
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index d4f6312..e37d9a1 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -30,5 +30,8 @@
 per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
 per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
 
+# Memory
+per-file com_android_server_am_OomConnection.cpp = file:/MEMORY_OWNERS
+
 # Bug component : 158088 = per-file *AnrTimer*
 per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 98482b8..c5b379b 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -565,8 +565,8 @@
 }
 
 static jboolean com_android_server_am_CachedAppOptimizer_isFreezerProfileValid(JNIEnv* env) {
-    int uid = getuid();
-    int pid = getpid();
+    uid_t uid = getuid();
+    pid_t pid = getpid();
 
     return isProfileValidForProcess("Frozen", uid, pid) &&
             isProfileValidForProcess("Unfrozen", uid, pid);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2c3f846..b14d37d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -71,6 +71,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
@@ -482,6 +483,7 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.net.module.util.ProxyUtils;
+import com.android.net.thread.flags.Flags;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
@@ -13834,6 +13836,11 @@
                 UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS});
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS});
+        if (Flags.threadUserRestrictionEnabled()) {
+            USER_RESTRICTION_PERMISSIONS.put(
+                    UserManager.DISALLOW_THREAD_NETWORK,
+                    new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK});
+        }
         USER_RESTRICTION_PERMISSIONS.put(
                 UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
         USER_RESTRICTION_PERMISSIONS.put(
diff --git a/services/incremental/OWNERS b/services/incremental/OWNERS
index 7ebb962..c18a9e5 100644
--- a/services/incremental/OWNERS
+++ b/services/incremental/OWNERS
@@ -1,8 +1,4 @@
 # Bug component: 554432
-include /services/core/java/com/android/server/pm/OWNERS
+include /PACKAGE_MANAGER_OWNERS
 
-alexbuy@google.com
-schfan@google.com
-toddke@google.com
-zyy@google.com
-patb@google.com
+zyy@google.com
\ No newline at end of file
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index 6e82907..fd21a32 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -21,6 +21,8 @@
 import android.Manifest;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
 import android.os.ISystemConfig;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -108,6 +110,15 @@
                     "Caller must hold " + Manifest.permission.QUERY_ALL_PACKAGES);
             return new ArrayList<>(SystemConfig.getInstance().getDefaultVrComponents());
         }
+
+        @Override
+        public List<String> getPreventUserDisablePackages() {
+            PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+            return SystemConfig.getInstance().getPreventUserDisablePackages().stream()
+                    .filter(preventUserDisablePackage ->
+                            pmi.canQueryPackage(Binder.getCallingUid(), preventUserDisablePackage))
+                    .collect(toList());
+        }
     };
 
     public SystemConfigService(Context context) {
diff --git a/services/profcollect/Android.bp b/services/profcollect/Android.bp
index 2040bb6..fe431f5 100644
--- a/services/profcollect/Android.bp
+++ b/services/profcollect/Android.bp
@@ -22,24 +22,25 @@
 }
 
 filegroup {
-  name: "services.profcollect-javasources",
-  srcs: ["src/**/*.java"],
-  path: "src",
-  visibility: ["//frameworks/base/services"],
+    name: "services.profcollect-javasources",
+    srcs: ["src/**/*.java"],
+    path: "src",
+    visibility: ["//frameworks/base/services"],
 }
 
 filegroup {
-  name: "services.profcollect-sources",
-  srcs: [
-    ":services.profcollect-javasources",
-    ":profcollectd_aidl",
-  ],
-  visibility: ["//frameworks/base/services:__subpackages__"],
+    name: "services.profcollect-sources",
+    srcs: [
+        ":services.profcollect-javasources",
+        ":profcollectd_aidl",
+    ],
+    visibility: ["//frameworks/base/services:__subpackages__"],
 }
 
 java_library_static {
-  name: "services.profcollect",
-  defaults: ["platform_service_defaults"],
-  srcs: [":services.profcollect-sources"],
-  libs: ["services.core"],
+    name: "services.profcollect",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.profcollect-sources"],
+    static_libs: ["services.core"],
+    libs: ["service-art.stubs.system_server"],
 }
diff --git a/services/profcollect/OWNERS b/services/profcollect/OWNERS
index b380e39..be9e61f 100644
--- a/services/profcollect/OWNERS
+++ b/services/profcollect/OWNERS
@@ -1,3 +1 @@
-srhines@google.com
-yabinc@google.com
-yikong@google.com
+include platform/prebuilts/clang/host/linux-x86:/OWNERS
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 4007672..68038fa 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -41,8 +41,10 @@
 import com.android.internal.R;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.IoThread;
+import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.art.ArtManagerLocal;
 import com.android.server.wm.ActivityMetricsLaunchObserver;
 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -59,7 +61,7 @@
     private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
     private static final String INTENT_UPLOAD_PROFILES =
             "com.android.server.profcollect.UPLOAD_PROFILES";
-    private static final long BG_PROCESS_PERIOD = TimeUnit.HOURS.toMillis(4); // every 4 hours.
+    private static final long BG_PROCESS_INTERVAL = TimeUnit.HOURS.toMillis(4); // every 4 hours.
 
     private IProfCollectd mIProfcollect;
     private static ProfcollectForwardingService sSelfService;
@@ -223,7 +225,7 @@
             js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME)
                     .setRequiresDeviceIdle(true)
                     .setRequiresCharging(true)
-                    .setPeriodic(BG_PROCESS_PERIOD)
+                    .setPeriodic(BG_PROCESS_INTERVAL)
                     .setPriority(JobInfo.PRIORITY_MIN)
                     .build());
         }
@@ -261,6 +263,7 @@
         BackgroundThread.get().getThreadHandler().post(
                 () -> {
                     registerAppLaunchObserver();
+                    registerDex2oatObserver();
                     registerOTAObserver();
                 });
     }
@@ -304,6 +307,44 @@
         }
     }
 
+    private void registerDex2oatObserver() {
+        ArtManagerLocal aml = LocalManagerRegistry.getManager(ArtManagerLocal.class);
+        if (aml == null) {
+            Log.w(LOG_TAG, "Couldn't get ArtManagerLocal");
+            return;
+        }
+        aml.setBatchDexoptStartCallback(Runnable::run,
+                (snapshot, reason, defaultPackages, builder, passedSignal) -> {
+                    traceOnDex2oatStart();
+                });
+    }
+
+    private void traceOnDex2oatStart() {
+        if (mIProfcollect == null) {
+            return;
+        }
+        // Sample for a fraction of dex2oat runs.
+        final int traceFrequency =
+            DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+                "dex2oat_trace_freq", 25);
+        int randomNum = ThreadLocalRandom.current().nextInt(100);
+        if (randomNum < traceFrequency) {
+            if (DEBUG) {
+                Log.d(LOG_TAG, "Tracing on dex2oat event");
+            }
+            BackgroundThread.get().getThreadHandler().post(() -> {
+                try {
+                    // Dex2oat could take a while before it starts. Add a short delay before start
+                    // tracing.
+                    Thread.sleep(1000);
+                    mIProfcollect.trace_once("dex2oat");
+                } catch (RemoteException | InterruptedException e) {
+                    Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
+                }
+            });
+        }
+    }
+
     private void registerOTAObserver() {
         UpdateEngine updateEngine = new UpdateEngine();
         updateEngine.bind(new UpdateEngineCallback() {
diff --git a/services/robotests/Android.bp b/services/robotests/Android.bp
index 52eae21..a70802a 100644
--- a/services/robotests/Android.bp
+++ b/services/robotests/Android.bp
@@ -57,9 +57,13 @@
     ],
     static_libs: [
         "androidx.test.ext.truth",
+        "Settings-robo-testutils",
+        "SettingsLib-robo-testutils",
     ],
 
     instrumentation_for: "FrameworksServicesLib",
+
+    upstream: true,
 }
 
 filegroup {
diff --git a/services/robotests/backup/Android.bp b/services/robotests/backup/Android.bp
index 66ee696..fba2cad 100644
--- a/services/robotests/backup/Android.bp
+++ b/services/robotests/backup/Android.bp
@@ -56,6 +56,8 @@
     // Include the testing libraries
     libs: [
         "mockito-robolectric-prebuilt",
+        "Settings-robo-testutils",
+        "SettingsLib-robo-testutils",
         "platform-test-annotations",
         "testng",
         "truth",
@@ -63,4 +65,6 @@
 
     instrumentation_for: "BackupFrameworksServicesLib",
 
+    upstream: true,
+
 }
diff --git a/services/robotests/backup/config/robolectric.properties b/services/robotests/backup/config/robolectric.properties
index 850557a..1ebf6d4 100644
--- a/services/robotests/backup/config/robolectric.properties
+++ b/services/robotests/backup/config/robolectric.properties
@@ -1 +1,3 @@
-sdk=NEWEST_SDK
\ No newline at end of file
+sdk=NEWEST_SDK
+looperMode=LEGACY
+shadows=com.android.server.testing.shadows.FrameworkShadowLooper
diff --git a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
index ee5a534..6839a06 100644
--- a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
@@ -57,6 +57,7 @@
             ShadowBackupDataOutput.class,
             ShadowEnvironment.class,
             ShadowFullBackup.class,
+            ShadowSigningInfo.class,
         })
 public class AppMetadataBackupWriterTest {
     private static final String TEST_PACKAGE = "com.test.package";
diff --git a/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java b/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java
new file mode 100644
index 0000000..53d807c
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 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.backup.fullbackup;
+
+import static android.os.Build.VERSION_CODES.P;
+
+import android.content.pm.SigningInfo;
+
+import org.robolectric.annotation.Implements;
+
+@Implements(value = SigningInfo.class, minSdk = P)
+public class ShadowSigningInfo {
+}
diff --git a/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java b/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
index 4949091..0092763 100644
--- a/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
+++ b/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
@@ -35,6 +35,7 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
 import org.robolectric.shadows.ShadowLooper;
 
 import java.util.concurrent.CountDownLatch;
@@ -45,6 +46,7 @@
  */
 @RunWith(RobolectricTestRunner.class)
 @Presubmit
+@LooperMode(LooperMode.Mode.LEGACY)
 public class NtpNetworkTimeHelperTest {
 
     private static final long MOCK_NTP_TIME = 1519930775453L;
diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
index 16d16cd..3681bd4 100644
--- a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
+++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
@@ -21,12 +21,15 @@
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.LooperShadowPicker;
+import org.robolectric.shadows.ShadowLegacyLooper;
 import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowPausedLooper;
 
 import java.util.Optional;
 
-@Implements(value = Looper.class)
-public class FrameworkShadowLooper extends ShadowLooper {
+@Implements(value = Looper.class, shadowPicker = FrameworkShadowLooper.Picker.class)
+public class FrameworkShadowLooper extends ShadowLegacyLooper {
     @RealObject private Looper mLooper;
     private Optional<Boolean> mIsCurrentThread = Optional.empty();
 
@@ -45,4 +48,10 @@
         }
         return Thread.currentThread() == mLooper.getThread();
     }
+
+    public static class Picker extends LooperShadowPicker<ShadowLooper> {
+        public Picker() {
+            super(FrameworkShadowLooper.class, ShadowPausedLooper.class);
+        }
+    }
 }
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
index 4a99486..1da6759 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
@@ -95,7 +95,6 @@
         sPackageAppEnabledStates.put(packageName, Integer.valueOf(newState));  // flags unused here.
     }
 
-    @Override
     protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
             throws NameNotFoundException {
         if (!sPackageInfos.containsKey(packageName)) {
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 528fda2..0af45ec 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_method_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
index e1fd2b3..8a12dcd 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_method_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerComponentOverrideTests/Android.bp b/services/tests/PackageManagerComponentOverrideTests/Android.bp
index 81fd90d..1227b0c 100644
--- a/services/tests/PackageManagerComponentOverrideTests/Android.bp
+++ b/services/tests/PackageManagerComponentOverrideTests/Android.bp
@@ -18,6 +18,7 @@
 // and this is more representative of a real caller. It also uses Mockito extended, and this
 // prevents converting the entire services test module.
 package {
+    default_team: "trendy_team_framework_android_packages",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index ad7af44..f15e533 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp
index 9921106..6da503d 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index 47d2422..84ff891 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -3,6 +3,7 @@
 //########################################################################
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
index e5be4d9..9e11fa2 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
@@ -50,7 +50,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 
-// atest PackageManagerServiceTest:com.android.server.pm.UserDataPreparerTest
+// atest PackageManagerServiceServerTests:com.android.server.pm.UserDataPreparerTest
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 @SmallTest
@@ -99,7 +99,7 @@
         systemDeDir.mkdirs();
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_DE);
         verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
-                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
+                eq(StorageManager.FLAG_STORAGE_DE));
         verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
                 eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
         int serialNumber = UserDataPreparer.getSerialNumber(userDeDir);
@@ -116,7 +116,7 @@
         systemCeDir.mkdirs();
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
         verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
-                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
+                eq(StorageManager.FLAG_STORAGE_CE));
         verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
                 eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
         int serialNumber = UserDataPreparer.getSerialNumber(userCeDir);
@@ -129,7 +129,7 @@
     public void testPrepareUserData_forNewUser_destroysOnFailure() throws Exception {
         TEST_USER.lastLoggedInTime = 0;
         doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
-                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL),
+                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
                         eq(StorageManager.FLAG_STORAGE_CE));
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
         verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
@@ -140,7 +140,7 @@
     public void testPrepareUserData_forExistingUser_doesNotDestroyOnFailure() throws Exception {
         TEST_USER.lastLoggedInTime = System.currentTimeMillis();
         doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
-                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL),
+                .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
                         eq(StorageManager.FLAG_STORAGE_CE));
         mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
         verify(mStorageManagerMock, never()).destroyUserStorage(isNull(String.class),
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index 85059838..c93f482 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index f56f290..8f25c0d 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -71,6 +71,7 @@
         // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
         "testng",
         "compatibility-device-util-axt",
+        "flag-junit",
     ],
 
     libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 9851bc1..b415682 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -16,24 +16,24 @@
 
 package com.android.server.trust;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.argThat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
-
 import android.Manifest;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustListener;
 import android.app.trust.ITrustManager;
 import android.content.BroadcastReceiver;
@@ -45,14 +45,26 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.net.Uri;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
-import android.os.test.TestLooper;
+import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
+import android.security.Authorization;
+import android.security.authorization.IKeystoreAuthorization;
 import android.service.trust.TrustAgentService;
 import android.testing.TestableContext;
 import android.view.IWindowManager;
@@ -61,12 +73,12 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 
-import com.google.android.collect.Lists;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -74,37 +86,81 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Random;
+import java.util.Collection;
+import java.util.List;
 
 public class TrustManagerServiceTest {
 
     @Rule
-    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .spyStatic(ActivityManager.class)
+            .spyStatic(Authorization.class)
+            .mockStatic(ServiceManager.class)
+            .mockStatic(WindowManagerGlobal.class)
+            .build();
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Rule
     public final MockContext mMockContext = new MockContext(
             ApplicationProvider.getApplicationContext());
 
     private static final String URI_SCHEME_PACKAGE = "package";
-    private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
+    private static final int TEST_USER_ID = 50;
+    private static final UserInfo TEST_USER =
+            new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL);
+    private static final int PARENT_USER_ID = 60;
+    private static final int PROFILE_USER_ID = 70;
+    private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L };
+    private static final long[] PROFILE_BIOMETRIC_SIDS = new long[] { 700L, 701L };
 
-    private final TestLooper mLooper = new TestLooper();
     private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>();
-    private final LockPatternUtils mLockPatternUtils = new LockPatternUtils(mMockContext);
-    private final TrustManagerService mService = new TrustManagerService(mMockContext);
+    private final ArrayList<ComponentName> mKnownTrustAgents = new ArrayList<>();
+    private final ArrayList<ComponentName> mEnabledTrustAgents = new ArrayList<>();
 
-    @Mock
-    private PackageManager mPackageManagerMock;
+    private @Mock ActivityManager mActivityManager;
+    private @Mock BiometricManager mBiometricManager;
+    private @Mock DevicePolicyManager mDevicePolicyManager;
+    private @Mock FaceManager mFaceManager;
+    private @Mock FingerprintManager mFingerprintManager;
+    private @Mock IKeystoreAuthorization mKeystoreAuthorization;
+    private @Mock LockPatternUtils mLockPatternUtils;
+    private @Mock PackageManager mPackageManager;
+    private @Mock UserManager mUserManager;
+    private @Mock IWindowManager mWindowManager;
+
+    private HandlerThread mHandlerThread;
+    private TrustManagerService.Injector mInjector;
+    private TrustManagerService mService;
+    private ITrustManager mTrustManager;
 
     @Before
-    public void setUp() {
-        resetTrustAgentLockSettings();
-        LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
+    public void setUp() throws Exception {
+        when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
+        doReturn(mock(IActivityManager.class)).when(() -> ActivityManager.getService());
+
+        when(mFaceManager.getSensorProperties()).thenReturn(List.of());
+        when(mFingerprintManager.getSensorProperties()).thenReturn(List.of());
+
+        doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
+
+        when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
+        when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
+        when(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).thenReturn(mKnownTrustAgents);
+        when(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).thenReturn(mEnabledTrustAgents);
+        doAnswer(invocation -> {
+            mKnownTrustAgents.clear();
+            mKnownTrustAgents.addAll((Collection<ComponentName>) invocation.getArgument(0));
+            return null;
+        }).when(mLockPatternUtils).setKnownTrustAgents(any(), eq(TEST_USER_ID));
+        doAnswer(invocation -> {
+            mEnabledTrustAgents.clear();
+            mEnabledTrustAgents.addAll((Collection<ComponentName>) invocation.getArgument(0));
+            return null;
+        }).when(mLockPatternUtils).setEnabledTrustAgents(any(), eq(TEST_USER_ID));
 
         ArgumentMatcher<Intent> trustAgentIntentMatcher = new ArgumentMatcher<Intent>() {
             @Override
@@ -112,17 +168,46 @@
                 return TrustAgentService.SERVICE_INTERFACE.equals(argument.getAction());
             }
         };
-        when(mPackageManagerMock.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher),
+        when(mPackageManager.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher),
                 anyInt(), anyInt())).thenReturn(mTrustAgentResolveInfoList);
-        when(mPackageManagerMock.checkPermission(any(), any())).thenReturn(
+        when(mPackageManager.checkPermission(any(), any())).thenReturn(
                 PackageManager.PERMISSION_GRANTED);
-        mMockContext.setMockPackageManager(mPackageManagerMock);
+
+        when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER));
+        when(mUserManager.getEnabledProfileIds(TEST_USER_ID)).thenReturn(new int[0]);
+        when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(TEST_USER);
+
+        when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+
+        mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
+        mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager);
+        mMockContext.addMockSystemService(FaceManager.class, mFaceManager);
+        mMockContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
+        mMockContext.setMockPackageManager(mPackageManager);
+        mMockContext.addMockSystemService(UserManager.class, mUserManager);
+        doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
+        LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
+
+        grantPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE);
+        grantPermission(Manifest.permission.TRUST_LISTENER);
+
+        mHandlerThread = new HandlerThread("handler");
+        mHandlerThread.start();
+        mInjector = new TrustManagerService.Injector(mLockPatternUtils, mHandlerThread.getLooper());
+        mService = new TrustManagerService(mMockContext, mInjector);
+
+        // Get the ITrustManager from the new TrustManagerService.
+        mService.onStart();
+        ArgumentCaptor<IBinder> binderArgumentCaptor = ArgumentCaptor.forClass(IBinder.class);
+        verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE),
+                    binderArgumentCaptor.capture(), anyBoolean(), anyInt()));
+        mTrustManager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
     }
 
     @After
     public void tearDown() {
-        resetTrustAgentLockSettings();
         LocalServices.removeServiceForTest(SystemServiceManager.class);
+        mHandlerThread.quit();
     }
 
     @Test
@@ -142,10 +227,9 @@
 
         bootService();
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                systemTrustAgent1, systemTrustAgent2);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                systemTrustAgent1, systemTrustAgent2, userTrustAgent1, userTrustAgent2);
+        assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1, systemTrustAgent2);
+        assertThat(mKnownTrustAgents).containsExactly(systemTrustAgent1, systemTrustAgent2,
+                    userTrustAgent1, userTrustAgent2);
     }
 
     @Test
@@ -162,10 +246,8 @@
 
         bootService();
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                defaultTrustAgent);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                systemTrustAgent, defaultTrustAgent);
+        assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent);
+        assertThat(mKnownTrustAgents).containsExactly(systemTrustAgent, defaultTrustAgent);
     }
 
     @Test
@@ -174,16 +256,16 @@
                 "com.android/.SystemTrustAgent");
         ComponentName trustAgent2 = ComponentName.unflattenFromString(
                 "com.android/.AnotherSystemTrustAgent");
-        initializeEnabledAgents(trustAgent1);
+        mEnabledTrustAgents.add(trustAgent1);
+        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+                Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
         addTrustAgent(trustAgent1, /* isSystemApp= */ true);
         addTrustAgent(trustAgent2, /* isSystemApp= */ true);
 
         bootService();
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                trustAgent1);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                trustAgent1, trustAgent2);
+        assertThat(mEnabledTrustAgents).containsExactly(trustAgent1);
+        assertThat(mKnownTrustAgents).containsExactly(trustAgent1, trustAgent2);
     }
 
     @Test
@@ -192,17 +274,17 @@
                 "com.android/.SystemTrustAgent");
         ComponentName trustAgent2 = ComponentName.unflattenFromString(
                 "com.android/.AnotherSystemTrustAgent");
-        initializeEnabledAgents(trustAgent1);
-        initializeKnownAgents(trustAgent1);
+        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+                Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
+        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+                Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
         addTrustAgent(trustAgent1, /* isSystemApp= */ true);
         addTrustAgent(trustAgent2, /* isSystemApp= */ true);
 
         bootService();
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                trustAgent1, trustAgent2);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                trustAgent1, trustAgent2);
+        assertThat(mEnabledTrustAgents).containsExactly(trustAgent1, trustAgent2);
+        assertThat(mKnownTrustAgents).containsExactly(trustAgent1, trustAgent2);
     }
 
     @Test
@@ -212,12 +294,10 @@
                 "com.android/.SystemTrustAgent");
         addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
 
-        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+        notifyPackageChanged(newAgentComponentName);
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                newAgentComponentName);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                newAgentComponentName);
+        assertThat(mEnabledTrustAgents).containsExactly(newAgentComponentName);
+        assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
     }
 
     @Test
@@ -233,12 +313,10 @@
                 "com.android/.SystemTrustAgent");
         addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
 
-        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+        notifyPackageChanged(newAgentComponentName);
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                defaultTrustAgent);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                defaultTrustAgent, newAgentComponentName);
+        assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent);
+        assertThat(mKnownTrustAgents).containsExactly(defaultTrustAgent, newAgentComponentName);
     }
 
     @Test
@@ -248,11 +326,10 @@
                 "com.user/.UserTrustAgent");
         addTrustAgent(newAgentComponentName, /* isSystemApp= */ false);
 
-        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+        notifyPackageChanged(newAgentComponentName);
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).isEmpty();
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                newAgentComponentName);
+        assertThat(mEnabledTrustAgents).isEmpty();
+        assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
     }
 
     @Test
@@ -265,50 +342,269 @@
         addTrustAgent(systemTrustAgent2, /* isSystemApp= */ true);
         bootService();
         // Simulate user turning off systemTrustAgent2
-        mLockPatternUtils.setEnabledTrustAgents(Collections.singletonList(systemTrustAgent1),
-                TEST_USER_ID);
+        mLockPatternUtils.setEnabledTrustAgents(List.of(systemTrustAgent1), TEST_USER_ID);
 
-        mMockContext.sendPackageChangedBroadcast(systemTrustAgent2);
+        notifyPackageChanged(systemTrustAgent2);
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                systemTrustAgent1);
+        assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1);
     }
 
     @Test
     public void reportEnabledTrustAgentsChangedInformsListener() throws RemoteException {
-        final LockPatternUtils utils = mock(LockPatternUtils.class);
-        final TrustManagerService service = new TrustManagerService(mMockContext,
-                new TrustManagerService.Injector(utils, mLooper.getLooper()));
         final ITrustListener trustListener = mock(ITrustListener.class);
-        final IWindowManager windowManager = mock(IWindowManager.class);
-        final int userId = new Random().nextInt();
+        mTrustManager.registerTrustListener(trustListener);
+        mService.waitForIdle();
+        mTrustManager.reportEnabledTrustAgentsChanged(TEST_USER_ID);
+        mService.waitForIdle();
+        verify(trustListener).onEnabledTrustAgentsChanged(TEST_USER_ID);
+    }
 
-        mMockContext.getTestablePermissions().setPermission(Manifest.permission.TRUST_LISTENER,
-                PERMISSION_GRANTED);
+    // Tests that when the device is locked for a managed profile with a *unified* challenge, the
+    // device locked notification that is sent to Keystore contains the biometric SIDs of the parent
+    // user, not the profile.  This matches the authentication that is needed to unlock the device
+    // for the profile again.
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testLockDeviceForManagedProfileWithUnifiedChallenge_usesParentBiometricSids()
+            throws Exception {
+        setupMocksForProfile(/* unifiedChallenge= */ true);
 
-        when(utils.getKnownTrustAgents(anyInt())).thenReturn(new ArrayList<>());
+        when(mWindowManager.isKeyguardLocked()).thenReturn(false);
+        mTrustManager.reportKeyguardShowingChanged();
+        verify(mKeystoreAuthorization).onDeviceUnlocked(PARENT_USER_ID, null);
+        verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
 
-        MockitoSession mockSession = mockitoSession()
-                .initMocks(this)
-                .mockStatic(ServiceManager.class)
-                .mockStatic(WindowManagerGlobal.class)
-                .startMocking();
+        when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+        mTrustManager.reportKeyguardShowingChanged();
+        verify(mKeystoreAuthorization)
+                .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
+        verify(mKeystoreAuthorization)
+                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
+    }
 
-        doReturn(windowManager).when(() -> {
-            WindowManagerGlobal.getWindowManagerService();
-        });
+    // Tests that when the device is locked for a managed profile with a *separate* challenge, the
+    // device locked notification that is sent to Keystore contains the biometric SIDs of the
+    // profile itself.  This matches the authentication that is needed to unlock the device for the
+    // profile again.
+    @Test
+    public void testLockDeviceForManagedProfileWithSeparateChallenge_usesProfileBiometricSids()
+            throws Exception {
+        setupMocksForProfile(/* unifiedChallenge= */ false);
 
-        service.onStart();
-        ArgumentCaptor<IBinder> binderArgumentCaptor = ArgumentCaptor.forClass(IBinder.class);
-        verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE),
-                binderArgumentCaptor.capture(), anyBoolean(), anyInt()));
-        ITrustManager manager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
-        manager.registerTrustListener(trustListener);
-        mLooper.dispatchAll();
-        manager.reportEnabledTrustAgentsChanged(userId);
-        mLooper.dispatchAll();
-        verify(trustListener).onEnabledTrustAgentsChanged(eq(userId));
-        mockSession.finishMocking();
+        mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, false);
+        verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+
+        mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
+        verify(mKeystoreAuthorization)
+                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS), eq(false));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenWeakFingerprintIsSetupAndAllowed()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenWeakFaceIsSetupAndAllowed() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenConvenienceFingerprintIsSetupAndAllowed()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_CONVENIENCE);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockEnabled_whenConvenienceFaceIsSetupAndAllowed()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_CONVENIENCE);
+        verifyWeakUnlockEnabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenStrongAuthRequired() throws Exception {
+        setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, true);
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenNonStrongBiometricNotAllowed() throws Exception {
+        setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED,
+                /* isNonStrongBiometricAllowed= */ false);
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenWeakFingerprintSensorIsPresentButNotEnrolled()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenWeakFaceSensorIsPresentButNotEnrolled()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void
+            testKeystoreWeakUnlockDisabled_whenWeakFingerprintIsSetupButForbiddenByDevicePolicy()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_WEAK);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenWeakFaceIsSetupButForbiddenByDevicePolicy()
+            throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_WEAK);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
+                .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFingerprintIsSetup() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFingerprint(SensorProperties.STRENGTH_STRONG);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFaceIsSetup() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        setupFace(SensorProperties.STRENGTH_STRONG);
+        verifyWeakUnlockDisabled();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testKeystoreWeakUnlockDisabled_whenNoBiometricsAreSetup() throws Exception {
+        setupStrongAuthTrackerToAllowEverything();
+        verifyWeakUnlockDisabled();
+    }
+
+    private void setupStrongAuthTrackerToAllowEverything() throws Exception {
+        setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED, true);
+    }
+
+    private void setupStrongAuthTracker(int strongAuthFlags, boolean isNonStrongBiometricAllowed)
+            throws Exception {
+        bootService();
+        mService.onUserSwitching(null, new SystemService.TargetUser(TEST_USER));
+
+        ArgumentCaptor<StrongAuthTracker> strongAuthTracker =
+                ArgumentCaptor.forClass(StrongAuthTracker.class);
+        verify(mLockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture());
+        strongAuthTracker.getValue().getStub().onStrongAuthRequiredChanged(
+                strongAuthFlags, TEST_USER_ID);
+        strongAuthTracker.getValue().getStub().onIsNonStrongBiometricAllowedChanged(
+                isNonStrongBiometricAllowed, TEST_USER_ID);
+        mService.waitForIdle();
+    }
+
+    private void setupFingerprint(int strength) {
+        setupFingerprint(strength, /* enrolled= */ true);
+    }
+
+    private void setupFingerprint(int strength, boolean enrolled) {
+        int sensorId = 100;
+        List<SensorProperties.ComponentInfo> componentInfo = List.of();
+        SensorProperties sensor = new SensorProperties(sensorId, strength, componentInfo);
+        when(mFingerprintManager.getSensorProperties()).thenReturn(List.of(sensor));
+        when(mFingerprintManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
+    }
+
+    private void setupFace(int strength) {
+        setupFace(strength, /* enrolled= */ true);
+    }
+
+    private void setupFace(int strength, boolean enrolled) {
+        int sensorId = 100;
+        List<SensorProperties.ComponentInfo> componentInfo = List.of();
+        FaceSensorProperties sensor = new FaceSensorProperties(
+                sensorId, strength, componentInfo, FaceSensorProperties.TYPE_RGB);
+        when(mFaceManager.getSensorProperties()).thenReturn(List.of(sensor));
+        when(mFaceManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
+    }
+
+    private void verifyWeakUnlockEnabled() throws Exception {
+        verifyWeakUnlockValue(true);
+    }
+
+    private void verifyWeakUnlockDisabled() throws Exception {
+        verifyWeakUnlockValue(false);
+    }
+
+    // Simulates a device unlock and a device lock, then verifies that the expected
+    // weakUnlockEnabled flag was passed to Keystore's onDeviceLocked method.
+    private void verifyWeakUnlockValue(boolean expectedWeakUnlockEnabled) throws Exception {
+        when(mWindowManager.isKeyguardLocked()).thenReturn(false);
+        mTrustManager.reportKeyguardShowingChanged();
+        verify(mKeystoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
+
+        when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+        mTrustManager.reportKeyguardShowingChanged();
+        verify(mKeystoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
+                eq(expectedWeakUnlockEnabled));
+    }
+
+    private void setupMocksForProfile(boolean unifiedChallenge) {
+        UserInfo parent = new UserInfo(PARENT_USER_ID, "parent", UserInfo.FLAG_FULL);
+        UserInfo profile = new UserInfo(PROFILE_USER_ID, "profile", UserInfo.FLAG_MANAGED_PROFILE);
+        when(mUserManager.getAliveUsers()).thenReturn(List.of(parent, profile));
+        when(mUserManager.getUserInfo(PARENT_USER_ID)).thenReturn(parent);
+        when(mUserManager.getUserInfo(PROFILE_USER_ID)).thenReturn(profile);
+        when(mUserManager.getProfileParent(PROFILE_USER_ID)).thenReturn(parent);
+        when(mUserManager.getEnabledProfileIds(PARENT_USER_ID))
+                .thenReturn(new int[] { PROFILE_USER_ID });
+
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        when(mLockPatternUtils.isProfileWithUnifiedChallenge(PROFILE_USER_ID))
+                .thenReturn(unifiedChallenge);
+        when(mLockPatternUtils.isManagedProfileWithUnifiedChallenge(PROFILE_USER_ID))
+                .thenReturn(unifiedChallenge);
+        when(mLockPatternUtils.isSeparateProfileChallengeEnabled(PROFILE_USER_ID))
+                .thenReturn(!unifiedChallenge);
+
+        when(mBiometricManager.getAuthenticatorIds(PARENT_USER_ID))
+                .thenReturn(PARENT_BIOMETRIC_SIDS);
+        when(mBiometricManager.getAuthenticatorIds(PROFILE_USER_ID))
+                .thenReturn(PROFILE_BIOMETRIC_SIDS);
+
+        bootService();
+        mService.onUserSwitching(null, new SystemService.TargetUser(parent));
     }
 
     private void addTrustAgent(ComponentName agentComponentName, boolean isSystemApp) {
@@ -327,33 +623,29 @@
         mTrustAgentResolveInfoList.add(resolveInfo);
     }
 
-    private void initializeEnabledAgents(ComponentName... enabledAgents) {
-        mLockPatternUtils.setEnabledTrustAgents(Lists.newArrayList(enabledAgents), TEST_USER_ID);
-        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
-                Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
-    }
-
-    private void initializeKnownAgents(ComponentName... knownAgents) {
-        mLockPatternUtils.setKnownTrustAgents(Lists.newArrayList(knownAgents), TEST_USER_ID);
-        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
-                Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
-    }
-
     private void bootService() {
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
         mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        mMockContext.sendUserStartedBroadcast();
     }
 
-    private void resetTrustAgentLockSettings() {
-        mLockPatternUtils.setEnabledTrustAgents(Collections.emptyList(), TEST_USER_ID);
-        mLockPatternUtils.setKnownTrustAgents(Collections.emptyList(), TEST_USER_ID);
+    private void grantPermission(String permission) {
+        mMockContext.getTestablePermissions().setPermission(
+                permission, PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void notifyPackageChanged(ComponentName changedComponent) {
+        mService.mPackageMonitor.onPackageChanged(
+                changedComponent.getPackageName(),
+                UserHandle.of(TEST_USER_ID).getUid(1234),
+                new String[] { changedComponent.getClassName() });
     }
 
     /** A mock Context that allows the test process to send protected broadcasts. */
     private static final class MockContext extends TestableContext {
 
-        private final ArrayList<BroadcastReceiver> mPackageChangedBroadcastReceivers =
+        private final ArrayList<BroadcastReceiver> mUserStartedBroadcastReceivers =
                 new ArrayList<>();
 
         MockContext(Context base) {
@@ -366,23 +658,22 @@
                 UserHandle user, IntentFilter filter, @Nullable String broadcastPermission,
                 @Nullable Handler scheduler) {
 
-            if (filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)) {
-                mPackageChangedBroadcastReceivers.add(receiver);
+            if (filter.hasAction(Intent.ACTION_USER_STARTED)) {
+                mUserStartedBroadcastReceivers.add(receiver);
             }
             return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
                     scheduler);
         }
 
-        void sendPackageChangedBroadcast(ComponentName changedComponent) {
-            Intent intent = new Intent(
-                    Intent.ACTION_PACKAGE_CHANGED,
-                    Uri.fromParts(URI_SCHEME_PACKAGE,
-                            changedComponent.getPackageName(), /* fragment= */ null))
-                    .putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
-                            new String[]{changedComponent.getClassName()})
-                    .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID)
-                    .putExtra(Intent.EXTRA_UID, UserHandle.of(TEST_USER_ID).getUid(1234));
-            for (BroadcastReceiver receiver : mPackageChangedBroadcastReceivers) {
+        @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user,
+                @Nullable String receiverPermission, @Nullable Bundle options) {
+        }
+
+        void sendUserStartedBroadcast() {
+            Intent intent = new Intent(Intent.ACTION_USER_STARTED)
+                    .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID);
+            for (BroadcastReceiver receiver : mUserStartedBroadcastReceivers) {
                 receiver.onReceive(this, intent);
             }
         }
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 01d8a9d..92628f4 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -78,6 +78,7 @@
         "coretests-aidl",
         "securebox",
         "flag-junit",
+        "net_flags_lib",
     ],
 
     libs: [
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index d26d671..f04a4e6 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -756,8 +756,7 @@
 
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
 
-        verify(mInjector.mStorageManagerMock, never())
-                .unlockCeStorage(eq(TEST_USER_ID), anyInt(), any());
+        verify(mInjector.mStorageManagerMock, never()).unlockCeStorage(eq(TEST_USER_ID), any());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index f5d50d1..6986cab 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -305,9 +305,9 @@
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
             mStorageManager.unlockCeStorage(/* userId= */ (int) args[0],
-                    /* secret= */ (byte[]) args[2]);
+                    /* secret= */ (byte[]) args[1]);
             return null;
-        }).when(sm).unlockCeStorage(anyInt(), anyInt(), any());
+        }).when(sm).unlockCeStorage(anyInt(), any());
 
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 5a62d92..7053597 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locksettings;
 
+import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
+
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
@@ -30,6 +32,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -38,6 +41,7 @@
 import android.app.PropertyInvalidatedCache;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.gatekeeper.GateKeeperResponse;
 import android.text.TextUtils;
 
@@ -45,10 +49,12 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsStateListener;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,6 +65,7 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() {
@@ -399,6 +406,60 @@
     }
 
     @Test
+    public void testVerifyCredential_notifyLockSettingsStateListeners_whenGoodPassword()
+            throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+        final LockscreenCredential password = newPassword("password");
+        setCredential(PRIMARY_USER_ID, password);
+        final LockSettingsStateListener listener = mock(LockSettingsStateListener.class);
+        mLocalService.registerLockSettingsStateListener(listener);
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                        .getResponseCode());
+
+        verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testVerifyCredential_notifyLockSettingsStateListeners_whenBadPassword()
+            throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+        final LockscreenCredential password = newPassword("password");
+        setCredential(PRIMARY_USER_ID, password);
+        final LockscreenCredential badPassword = newPassword("badPassword");
+        final LockSettingsStateListener listener = mock(LockSettingsStateListener.class);
+        mLocalService.registerLockSettingsStateListener(listener);
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
+                        .getResponseCode());
+
+        verify(listener).onAuthenticationFailed(PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testLockSettingsStateListener_registeredThenUnregistered() throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
+        final LockscreenCredential password = newPassword("password");
+        setCredential(PRIMARY_USER_ID, password);
+        final LockscreenCredential badPassword = newPassword("badPassword");
+        final LockSettingsStateListener listener = mock(LockSettingsStateListener.class);
+
+        mLocalService.registerLockSettingsStateListener(listener);
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */)
+                        .getResponseCode());
+        verify(listener).onAuthenticationSucceeded(PRIMARY_USER_ID);
+
+        mLocalService.unregisterLockSettingsStateListener(listener);
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(badPassword, PRIMARY_USER_ID, 0 /* flags */)
+                        .getResponseCode());
+        verify(listener, never()).onAuthenticationFailed(PRIMARY_USER_ID);
+    }
+
+    @Test
     public void testSetCredentialNotPossibleInSecureFrpModeDuringSuw() {
         setUserSetupComplete(false);
         setSecureFrpMode(true);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index eca19c8..2da2f50 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -506,6 +506,14 @@
     }
 
     @Test
+    public void testUnlockUserWithTokenWithBadHandleReturnsFalse() {
+        final long badTokenHandle = 123456789;
+        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+        assertFalse(mLocalService.unlockUserWithToken(badTokenHandle, token, PRIMARY_USER_ID));
+    }
+
+    @Test
     public void testGetHashFactorPrimaryUser() throws RemoteException {
         LockscreenCredential password = newPassword("password");
         initSpAndSetCredential(PRIMARY_USER_ID, password);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
index 13dc120..d6d2b6d 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.net;
 
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -327,12 +328,20 @@
         isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
         expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
 
+        // Background chain
+        final ArrayMap<Integer, Boolean> isRestrictedInBackground = new ArrayMap<>();
+        isRestrictedInBackground.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedInBackground.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedInBackground.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_BACKGROUND, isRestrictedInBackground);
+
         final int[] chains = {
                 FIREWALL_CHAIN_STANDBY,
                 FIREWALL_CHAIN_POWERSAVE,
                 FIREWALL_CHAIN_DOZABLE,
                 FIREWALL_CHAIN_RESTRICTED,
-                FIREWALL_CHAIN_LOW_POWER_STANDBY
+                FIREWALL_CHAIN_LOW_POWER_STANDBY,
+                FIREWALL_CHAIN_BACKGROUND
         };
         final int[] states = {
                 INetd.FIREWALL_RULE_ALLOW,
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 2a76452..4451cae 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -26,12 +26,14 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
 import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -48,8 +50,13 @@
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NOT_IN_BACKGROUND;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP;
+import static android.net.NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
@@ -64,6 +71,7 @@
 import static android.net.NetworkTemplate.MATCH_CARRIER;
 import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.os.PowerExemptionManager.REASON_OTHER;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
@@ -146,6 +154,8 @@
 import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.PersistableBundle;
+import android.os.PowerExemptionManager;
+import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.RemoteException;
@@ -153,6 +163,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -169,6 +182,7 @@
 import android.util.Range;
 import android.util.RecurrenceRule;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
@@ -243,6 +257,9 @@
 public class NetworkPolicyManagerServiceTest {
     private static final String TAG = "NetworkPolicyManagerServiceTest";
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final long TEST_START = 1194220800000L;
     private static final String TEST_IFACE = "test0";
     private static final String TEST_WIFI_NETWORK_KEY = "TestWifiNetworkKey";
@@ -285,6 +302,7 @@
     private @Mock TelephonyManager mTelephonyManager;
     private @Mock UserManager mUserManager;
     private @Mock NetworkStatsManager mStatsManager;
+    private @Mock PowerExemptionManager mPowerExemptionManager;
     private TestDependencies mDeps;
 
     private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor =
@@ -302,6 +320,7 @@
     private NetworkPolicyManagerService mService;
 
     private final ArraySet<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
+    private BroadcastReceiver mPowerAllowlistReceiver;
 
     /**
      * In some of the tests while initializing NetworkPolicyManagerService,
@@ -446,6 +465,7 @@
     @Before
     public void callSystemReady() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean())).thenReturn(new int[0]);
 
         final Context context = InstrumentationRegistry.getContext();
 
@@ -482,6 +502,8 @@
                         return mUserManager;
                     case Context.NETWORK_STATS_SERVICE:
                         return mStatsManager;
+                    case Context.POWER_EXEMPTION_SERVICE:
+                        return mPowerExemptionManager;
                     default:
                         return super.getSystemService(name);
                 }
@@ -495,6 +517,9 @@
             @Override
             public Intent registerReceiver(BroadcastReceiver receiver,
                     IntentFilter filter, String broadcastPermission, Handler scheduler) {
+                if (filter.hasAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED)) {
+                    mPowerAllowlistReceiver = receiver;
+                }
                 mRegisteredReceivers.add(receiver);
                 return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
             }
@@ -2066,6 +2091,12 @@
         expectHasUseRestrictedNetworksPermission(UID_A, true);
         expectHasUseRestrictedNetworksPermission(UID_B, false);
 
+        // Set low enough proc-states to ensure these uids are allowed in the background chain.
+        // To maintain clean separation between separate firewall chains, the tests could
+        // check for the specific blockedReasons in the uidBlockedState.
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, 21);
+        callAndWaitOnUidStateChanged(UID_B, BACKGROUND_THRESHOLD_STATE - 1, 21);
+
         Map<Integer, Integer> firewallUidRules = new ArrayMap<>();
         doAnswer(arg -> {
             int[] uids = arg.getArgument(1);
@@ -2113,7 +2144,111 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainEnabled() throws Exception {
+        verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
+    }
+
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainOnProcStateChange() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+
+        mService.mBackgroundRestrictionDelayMs = 500; // To avoid waiting too long in tests.
+
+        // The app will be blocked when there is no prior proc-state.
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        int procStateSeq = 23;
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, procStateSeq++);
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_ALLOW);
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq++);
+
+        // The app should be blocked after a delay. Posting a message just after the delay and
+        // waiting for it to complete to ensure that the blocking code has executed.
+        waitForDelayedMessageOnHandler(mService.mBackgroundRestrictionDelayMs + 1);
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_DEFAULT);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainOnAllowlistChange() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+
+        // The apps will be blocked when there is no prior proc-state.
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+        assertTrue(mService.isUidNetworkingBlocked(UID_B, false));
+
+        final int procStateSeq = 29;
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+                .thenReturn(new int[]{APP_ID_A, APP_ID_B});
+        final SparseIntArray firewallUidRules = new SparseIntArray();
+        doAnswer(arg -> {
+            final int[] uids = arg.getArgument(1);
+            final int[] rules = arg.getArgument(2);
+            assertTrue(uids.length == rules.length);
+
+            for (int i = 0; i < uids.length; ++i) {
+                firewallUidRules.put(uids[i], rules[i]);
+            }
+            return null;
+        }).when(mNetworkManager).setFirewallUidRules(eq(FIREWALL_CHAIN_BACKGROUND),
+                any(int[].class), any(int[].class));
+
+        mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+        assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_A, -1));
+        assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_B, -1));
+
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+        assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainOnTempAllowlistChange() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+
+        // The app will be blocked as is no prior proc-state.
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        final int procStateSeq = 19;
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        final NetworkPolicyManagerInternal internal = LocalServices.getService(
+                NetworkPolicyManagerInternal.class);
+
+        internal.onTempPowerSaveWhitelistChange(APP_ID_A, true, REASON_OTHER, "testing");
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_ALLOW);
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+        internal.onTempPowerSaveWhitelistChange(APP_ID_A, false, REASON_OTHER, "testing");
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_DEFAULT);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+    }
+
+    @Test
     public void testLowPowerStandbyAllowlist() throws Exception {
+        // Chain background is also enabled but these procstates are important enough to be exempt.
         callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0);
         callAndWaitOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
         callAndWaitOnUidStateChanged(UID_C, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
@@ -2200,7 +2335,21 @@
                 ALLOWED_REASON_TOP), BLOCKED_REASON_NONE);
         effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
                 ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST), BLOCKED_REASON_NONE);
-        // TODO: test more combinations of blocked reasons.
+
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                ALLOWED_REASON_NOT_IN_BACKGROUND), BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND
+                        | BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_NOT_IN_BACKGROUND),
+                BLOCKED_REASON_BATTERY_SAVER);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND
+                        | BLOCKED_REASON_DOZE, ALLOWED_REASON_NOT_IN_BACKGROUND),
+                BLOCKED_REASON_DOZE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                        ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS), BLOCKED_REASON_APP_BACKGROUND);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                ALLOWED_REASON_POWER_SAVE_ALLOWLIST), BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST), BLOCKED_REASON_NONE);
 
         for (Map.Entry<Pair<Integer, Integer>, Integer> test : effectiveBlockedReasons.entrySet()) {
             final int expectedEffectiveBlockedReasons = test.getValue();
@@ -2529,7 +2678,6 @@
     private FutureIntent mRestrictBackgroundChanged;
 
     private void postMsgAndWaitForCompletion() throws InterruptedException {
-        final Handler handler = mService.getHandlerForTesting();
         final CountDownLatch latch = new CountDownLatch(1);
         mService.getHandlerForTesting().post(latch::countDown);
         if (!latch.await(5, TimeUnit.SECONDS)) {
@@ -2537,6 +2685,14 @@
         }
     }
 
+    private void waitForDelayedMessageOnHandler(long delayMs) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mService.getHandlerForTesting().postDelayed(latch::countDown, delayMs);
+        if (!latch.await(delayMs + 5_000, TimeUnit.MILLISECONDS)) {
+            fail("Timed out waiting for delayed msg to be handled");
+        }
+    }
+
     private void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage)
             throws InterruptedException {
         mService.setSubscriptionPlans(subId, plans, 0, callingPackage);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 6a6fa3f..189dbdc 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1104,6 +1104,11 @@
         verify(mAlarmManager).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
         assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
                 captor.getValue().getIntent().getPackage());
+
+        mService.cancelScheduledTimeoutLocked(r);
+        verify(mAlarmManager).cancel(captor.capture());
+        assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME,
+                captor.getValue().getIntent().getPackage());
     }
 
     @Test
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
index 336bfdd..a8fd6f2 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
@@ -35,11 +35,13 @@
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Keep;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.RingBuffer;
 import com.android.server.usage.BroadcastResponseStatsTracker.NotificationEventType;
 
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
 public class BroadcastResponseStatsLogger {
 
     private static final int MAX_LOG_SIZE =
@@ -49,10 +51,10 @@
 
     @GuardedBy("mLock")
     private final LogBuffer mBroadcastEventsBuffer = new LogBuffer(
-            BroadcastEvent.class, MAX_LOG_SIZE);
+            BroadcastEvent::new, BroadcastEvent[]::new, MAX_LOG_SIZE);
     @GuardedBy("mLock")
     private final LogBuffer mNotificationEventsBuffer = new LogBuffer(
-            NotificationEvent.class, MAX_LOG_SIZE);
+            NotificationEvent::new, NotificationEvent[]::new, MAX_LOG_SIZE);
 
     void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
             UserHandle targetUser, long idForResponseEvent,
@@ -96,8 +98,8 @@
 
     private static final class LogBuffer<T extends Data> extends RingBuffer<T> {
 
-        LogBuffer(Class<T> classType, int capacity) {
-            super(classType, capacity);
+        LogBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) {
+            super(newItem, newBacking, capacity);
         }
 
         void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
@@ -179,8 +181,7 @@
         }
     }
 
-    @Keep
-    public static final class BroadcastEvent implements Data {
+    private static final class BroadcastEvent implements Data {
         public int sourceUid;
         public int targetUserId;
         public int targetUidProcessState;
@@ -200,8 +201,7 @@
         }
     }
 
-    @Keep
-    public static final class NotificationEvent implements Data {
+    private static final class NotificationEvent implements Data {
         public int type;
         public String packageName;
         public int userId;
@@ -218,7 +218,7 @@
         }
     }
 
-    public interface Data {
+    private interface Data {
         void reset();
     }
 }
diff --git a/telecomm/OWNERS b/telecomm/OWNERS
index b57b7c7..bb2ac51 100644
--- a/telecomm/OWNERS
+++ b/telecomm/OWNERS
@@ -6,4 +6,5 @@
 rgreenwalt@google.com
 grantmenke@google.com
 pmadapurmath@google.com
-tjstuart@google.com
\ No newline at end of file
+tjstuart@google.com
+huiwang@google.com
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 287aa65..7607c64 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -15,4 +15,4 @@
 per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com
 
 #Domain Selection is jointly owned, add additional owners for domain selection specific files
-per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com
+per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com,jdyou@google.com
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 527159a..70a9540 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -120,12 +120,13 @@
     path: "src",
 }
 
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
 // ========================================================================
 filegroup {
     name: "android-test-base-current.txt",
     visibility: [
         "//cts/tests/signature/api",
+        "//vendor:__subpackages__",
     ],
     srcs: [
         "api/current.txt",
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 22320fd..7c4d12e 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -54,12 +54,13 @@
     dist_group: "android",
 }
 
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
 // ========================================================================
 filegroup {
     name: "android-test-mock-current.txt",
     visibility: [
         "//cts/tests/signature/api",
+        "//vendor:__subpackages__",
     ],
     srcs: [
         "api/current.txt",
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 13a5dac..21e09d3 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -79,12 +79,13 @@
     ],
 }
 
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
 // ========================================================================
 filegroup {
     name: "android-test-runner-current.txt",
     visibility: [
         "//cts/tests/signature/api",
+        "//vendor:__subpackages__",
     ],
     srcs: [
         "api/current.txt",
diff --git a/tests/Camera2Tests/CameraToo/tests/Android.bp b/tests/Camera2Tests/CameraToo/tests/Android.bp
new file mode 100644
index 0000000..8339a2c
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2014 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "frameworks_base_license",
+    ],
+}
+
+android_test {
+    name: "CameraTooTests",
+    instrumentation_for: "CameraToo",
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    static_libs: [
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+    ],
+    data: [
+        ":CameraToo",
+    ],
+}
diff --git a/tests/Camera2Tests/CameraToo/tests/Android.mk b/tests/Camera2Tests/CameraToo/tests/Android.mk
deleted file mode 100644
index dfa64f1..0000000
--- a/tests/Camera2Tests/CameraToo/tests/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (C) 2014 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.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := CameraTooTests
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../NOTICE
-LOCAL_INSTRUMENTATION_FOR := CameraToo
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules mockito-target-minus-junit4
-
-include $(BUILD_PACKAGE)
diff --git a/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml b/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml
new file mode 100644
index 0000000..884c095
--- /dev/null
+++ b/tests/Camera2Tests/CameraToo/tests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs CameraToo tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CameraTooTests.apk" />
+        <option name="test-file-name" value="CameraToo.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.example.android.camera2.cameratoo.tests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 292fbcb..d996ee6 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_input_framework",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/tests/InputMethodStressTest/Android.bp b/tests/InputMethodStressTest/Android.bp
index 58ceb3f..5ed8d8d 100644
--- a/tests/InputMethodStressTest/Android.bp
+++ b/tests/InputMethodStressTest/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_input_method_framework",
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 23efe54..2751141 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_framework_android_packages",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index ddb3649..12d4338 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_android_gpu",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
index ed34fa9..0f21035 100644
--- a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
+++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_android_gpu",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp
index 0bdb3a8..38355530 100644
--- a/tests/UpdatableSystemFontTest/testdata/Android.bp
+++ b/tests/UpdatableSystemFontTest/testdata/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_android_gpu",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index f846164..20b7f1f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -269,6 +269,7 @@
     @Test
     public void testCreatedTransformsAreApplied() throws Exception {
         verifyVcnTransformsApplied(mGatewayConnection, false /* expectForwardTransform */);
+        verify(mUnderlyingNetworkController).updateInboundTransform(any(), any());
     }
 
     @Test
@@ -327,6 +328,8 @@
                             eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
         }
 
+        verify(mUnderlyingNetworkController).updateInboundTransform(any(), any());
+
         assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
 
         final List<ChildSaProposal> saProposals =
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 692c8a8..49665f7 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -27,15 +27,18 @@
 import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
 
 import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
+import static com.android.server.vcn.VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS;
 import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
 import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
 import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
@@ -55,6 +58,7 @@
 import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
+import android.net.vcn.VcnManager;
 import android.net.vcn.VcnTransportInfo;
 import android.net.wifi.WifiInfo;
 import android.os.ParcelUuid;
@@ -81,6 +85,7 @@
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /** Tests for TelephonySubscriptionTracker */
 @RunWith(AndroidJUnit4.class)
@@ -352,4 +357,71 @@
                         any(Executor.class),
                         any(ConnectivityDiagnosticsCallback.class));
     }
+
+    private void verifyGetSafeModeTimeoutMs(
+            boolean isInTestMode,
+            boolean isConfigTimeoutSupported,
+            PersistableBundleWrapper carrierConfig,
+            long expectedTimeoutMs)
+            throws Exception {
+        doReturn(isInTestMode).when(mVcnContext).isInTestMode();
+        doReturn(isConfigTimeoutSupported).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
+
+        final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
+        doReturn(carrierConfig).when(snapshot).getCarrierConfigForSubGrp(TEST_SUB_GRP);
+
+        final long result =
+                VcnGatewayConnection.getSafeModeTimeoutMs(mVcnContext, snapshot, TEST_SUB_GRP);
+
+        assertEquals(expectedTimeoutMs, result);
+    }
+
+    @Test
+    public void testGetSafeModeTimeoutMs_configTimeoutUnsupported() throws Exception {
+        verifyGetSafeModeTimeoutMs(
+                false /* isInTestMode */,
+                false /* isConfigTimeoutSupported */,
+                null /* carrierConfig */,
+                TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+    }
+
+    @Test
+    public void testGetSafeModeTimeoutMs_configTimeoutSupported() throws Exception {
+        final int carrierConfigTimeoutSeconds = 20;
+        final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class);
+        doReturn(carrierConfigTimeoutSeconds)
+                .when(carrierConfig)
+                .getInt(eq(VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY), anyInt());
+
+        verifyGetSafeModeTimeoutMs(
+                false /* isInTestMode */,
+                true /* isConfigTimeoutSupported */,
+                carrierConfig,
+                TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
+    }
+
+    @Test
+    public void testGetSafeModeTimeoutMs_configTimeoutSupported_carrierConfigNull()
+            throws Exception {
+        verifyGetSafeModeTimeoutMs(
+                false /* isInTestMode */,
+                true /* isConfigTimeoutSupported */,
+                null /* carrierConfig */,
+                TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+    }
+
+    @Test
+    public void testGetSafeModeTimeoutMs_configTimeoutOverrideTestModeDefault() throws Exception {
+        final int carrierConfigTimeoutSeconds = 20;
+        final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class);
+        doReturn(carrierConfigTimeoutSeconds)
+                .when(carrierConfig)
+                .getInt(eq(VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY), anyInt());
+
+        verifyGetSafeModeTimeoutMs(
+                true /* isInTestMode */,
+                true /* isConfigTimeoutSupported */,
+                carrierConfig,
+                TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index edced87..e29e462 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -67,6 +67,8 @@
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
+import com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
+import com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
 import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController;
 import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
@@ -118,13 +120,7 @@
             NetworkCapabilities networkCapabilities,
             LinkProperties linkProperties,
             boolean isBlocked) {
-        return new UnderlyingNetworkRecord(
-                network,
-                networkCapabilities,
-                linkProperties,
-                isBlocked,
-                false /* isSelected */,
-                0 /* priorityClass */);
+        return new UnderlyingNetworkRecord(network, networkCapabilities, linkProperties, isBlocked);
     }
 
     protected static final String TEST_TCP_BUFFER_SIZES_1 = "1,2,3,4";
@@ -226,6 +222,9 @@
         doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
         doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
         doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
+        doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
+        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
+        doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled();
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
new file mode 100644
index 0000000..9daba6a
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn.routeselection;
+
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
+
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.PACKET_LOSS_UNAVALAIBLE;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.net.IpSecTransformState;
+import android.os.OutcomeReceiver;
+import android.os.PowerManager;
+
+import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator;
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper;
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.concurrent.TimeUnit;
+
+public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
+    private static final String TAG = IpSecPacketLossDetectorTest.class.getSimpleName();
+
+    private static final int REPLAY_BITMAP_LEN_BYTE = 512;
+    private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8;
+    private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5;
+    private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L);
+
+    @Mock private IpSecTransformWrapper mIpSecTransform;
+    @Mock private NetworkMetricMonitorCallback mMetricMonitorCallback;
+    @Mock private PersistableBundleWrapper mCarrierConfig;
+    @Mock private IpSecPacketLossDetector.Dependencies mDependencies;
+    @Spy private PacketLossCalculator mPacketLossCalculator = new PacketLossCalculator();
+
+    @Captor private ArgumentCaptor<OutcomeReceiver> mTransformStateReceiverCaptor;
+    @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+
+    private IpSecPacketLossDetector mIpSecPacketLossDetector;
+    private IpSecTransformState mTransformStateInitial;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mTransformStateInitial = newTransformState(0, 0, newReplayBitmap(0));
+
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY), anyInt()))
+                .thenReturn((int) TimeUnit.MILLISECONDS.toSeconds(POLL_IPSEC_STATE_INTERVAL_MS));
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
+                        anyInt()))
+                .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+
+        when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator);
+
+        mIpSecPacketLossDetector =
+                new IpSecPacketLossDetector(
+                        mVcnContext,
+                        mNetwork,
+                        mCarrierConfig,
+                        mMetricMonitorCallback,
+                        mDependencies);
+    }
+
+    private static IpSecTransformState newTransformState(
+            long rxSeqNo, long packtCount, byte[] replayBitmap) {
+        return new IpSecTransformState.Builder()
+                .setRxHighestSequenceNumber(rxSeqNo)
+                .setPacketCount(packtCount)
+                .setReplayBitmap(replayBitmap)
+                .build();
+    }
+
+    private static byte[] newReplayBitmap(int receivedPktCnt) {
+        final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT);
+        for (int i = 0; i < receivedPktCnt; i++) {
+            bitSet.set(i);
+        }
+        return Arrays.copyOf(bitSet.toByteArray(), REPLAY_BITMAP_LEN_BYTE);
+    }
+
+    private void verifyStopped() {
+        assertFalse(mIpSecPacketLossDetector.isStarted());
+        assertFalse(mIpSecPacketLossDetector.isValidationFailed());
+        assertNull(mIpSecPacketLossDetector.getLastTransformState());
+
+        // No event scheduled
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testInitialization() throws Exception {
+        assertFalse(mIpSecPacketLossDetector.isSelectedUnderlyingNetwork());
+        verifyStopped();
+    }
+
+    private OutcomeReceiver<IpSecTransformState, RuntimeException>
+            startMonitorAndCaptureStateReceiver() {
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+        // Trigger the runnable
+        mTestLooper.dispatchAll();
+
+        verify(mIpSecTransform)
+                .getIpSecTransformState(any(), mTransformStateReceiverCaptor.capture());
+        return mTransformStateReceiverCaptor.getValue();
+    }
+
+    @Test
+    public void testStartMonitor() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+
+        assertTrue(mIpSecPacketLossDetector.isStarted());
+        assertFalse(mIpSecPacketLossDetector.isValidationFailed());
+        assertTrue(mIpSecPacketLossDetector.isSelectedUnderlyingNetwork());
+        assertEquals(mIpSecTransform, mIpSecPacketLossDetector.getInboundTransformInternal());
+
+        // Mock receiving a state
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+
+        // Verify the first polled state is stored
+        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+        verify(mPacketLossCalculator, never())
+                .getPacketLossRatePercentage(any(), any(), anyString());
+
+        // Verify next poll is scheduled
+        assertNull(mTestLooper.nextMessage());
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        assertNotNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testStartedMonitor_enterDozeMoze() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+
+        // Mock receiving a state
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+
+        // Mock entering doze mode
+        final Intent intent = mock(Intent.class);
+        when(intent.getAction()).thenReturn(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        when(mPowerManagerService.isDeviceIdleMode()).thenReturn(true);
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), any(), any(), any());
+        final BroadcastReceiver broadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        broadcastReceiver.onReceive(mContext, intent);
+
+        assertNull(mIpSecPacketLossDetector.getLastTransformState());
+    }
+
+    @Test
+    public void testStartedMonitor_updateInboundTransform() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+
+        // Mock receiving a state
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+        assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+
+        // Update the inbound transform
+        final IpSecTransformWrapper newTransform = mock(IpSecTransformWrapper.class);
+        mIpSecPacketLossDetector.setInboundTransformInternal(newTransform);
+
+        // Verifications
+        assertNull(mIpSecPacketLossDetector.getLastTransformState());
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        mTestLooper.dispatchAll();
+        verify(newTransform).getIpSecTransformState(any(), any());
+    }
+
+    @Test
+    public void testStartedMonitor_updateCarrierConfig() throws Exception {
+        startMonitorAndCaptureStateReceiver();
+
+        final int additionalPollIntervalMs = (int) TimeUnit.SECONDS.toMillis(10L);
+        when(mCarrierConfig.getInt(
+                        eq(VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY), anyInt()))
+                .thenReturn(
+                        (int)
+                                TimeUnit.MILLISECONDS.toSeconds(
+                                        POLL_IPSEC_STATE_INTERVAL_MS + additionalPollIntervalMs));
+        mIpSecPacketLossDetector.setCarrierConfig(mCarrierConfig);
+        mTestLooper.dispatchAll();
+
+        // The already scheduled event is still fired with the old timeout
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        mTestLooper.dispatchAll();
+
+        // The next scheduled event will take 10 more seconds to fire
+        mTestLooper.moveTimeForward(POLL_IPSEC_STATE_INTERVAL_MS);
+        assertNull(mTestLooper.nextMessage());
+        mTestLooper.moveTimeForward(additionalPollIntervalMs);
+        assertNotNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testStopMonitor() throws Exception {
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+        assertTrue(mIpSecPacketLossDetector.isStarted());
+        assertNotNull(mTestLooper.nextMessage());
+
+        // Unselect the monitor
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(false /* setIsSelected */);
+        verifyStopped();
+    }
+
+    @Test
+    public void testClose() throws Exception {
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */);
+        mIpSecPacketLossDetector.setInboundTransformInternal(mIpSecTransform);
+
+        assertTrue(mIpSecPacketLossDetector.isStarted());
+        assertNotNull(mTestLooper.nextMessage());
+
+        // Stop the monitor
+        mIpSecPacketLossDetector.close();
+        verifyStopped();
+        verify(mIpSecTransform).close();
+    }
+
+    @Test
+    public void testTransformStateReceiverOnResultWhenStopped() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+
+        // Unselect the monitor
+        mIpSecPacketLossDetector.setIsSelectedUnderlyingNetwork(false /* setIsSelected */);
+        verifyStopped();
+
+        xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1)));
+        verify(mPacketLossCalculator, never())
+                .getPacketLossRatePercentage(any(), any(), anyString());
+    }
+
+    @Test
+    public void testTransformStateReceiverOnError() throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+
+        xfrmStateReceiver.onError(new RuntimeException("Test"));
+        verify(mPacketLossCalculator, never())
+                .getPacketLossRatePercentage(any(), any(), anyString());
+    }
+
+    private void checkHandleLossRate(
+            int mockPacketLossRate, boolean isLastStateExpectedToUpdate, boolean isCallbackExpected)
+            throws Exception {
+        final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
+                startMonitorAndCaptureStateReceiver();
+        doReturn(mockPacketLossRate)
+                .when(mPacketLossCalculator)
+                .getPacketLossRatePercentage(any(), any(), anyString());
+
+        // Mock receiving two states with mTransformStateInitial and an arbitrary transformNew
+        final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1));
+        xfrmStateReceiver.onResult(mTransformStateInitial);
+        xfrmStateReceiver.onResult(transformNew);
+
+        // Verifications
+        verify(mPacketLossCalculator)
+                .getPacketLossRatePercentage(
+                        eq(mTransformStateInitial), eq(transformNew), anyString());
+
+        if (isLastStateExpectedToUpdate) {
+            assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState());
+        } else {
+            assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
+        }
+
+        if (isCallbackExpected) {
+            verify(mMetricMonitorCallback).onValidationResultReceived();
+        } else {
+            verify(mMetricMonitorCallback, never()).onValidationResultReceived();
+        }
+    }
+
+    @Test
+    public void testHandleLossRate_validationPass() throws Exception {
+        checkHandleLossRate(
+                2, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+    }
+
+    @Test
+    public void testHandleLossRate_validationFail() throws Exception {
+        checkHandleLossRate(
+                22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+    }
+
+    @Test
+    public void testHandleLossRate_resultUnavalaible() throws Exception {
+        checkHandleLossRate(
+                PACKET_LOSS_UNAVALAIBLE,
+                false /* isLastStateExpectedToUpdate */,
+                false /* isCallbackExpected */);
+    }
+
+    private void checkGetPacketLossRate(
+            IpSecTransformState oldState, IpSecTransformState newState, int expectedLossRate)
+            throws Exception {
+        assertEquals(
+                expectedLossRate,
+                mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG));
+    }
+
+    private void checkGetPacketLossRate(
+            IpSecTransformState oldState,
+            int rxSeqNo,
+            int packetCount,
+            int packetInWin,
+            int expectedDataLossRate)
+            throws Exception {
+        final IpSecTransformState newState =
+                newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
+        checkGetPacketLossRate(oldState, newState, expectedDataLossRate);
+    }
+
+    @Test
+    public void testGetPacketLossRate_replayWindowUnchanged() throws Exception {
+        checkGetPacketLossRate(
+                mTransformStateInitial, mTransformStateInitial, PACKET_LOSS_UNAVALAIBLE);
+        checkGetPacketLossRate(mTransformStateInitial, 3000, 2000, 2000, PACKET_LOSS_UNAVALAIBLE);
+    }
+
+    @Test
+    public void testGetPacketLossRate_againstInitialState() throws Exception {
+        checkGetPacketLossRate(mTransformStateInitial, 7000, 7001, 4096, 0);
+        checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4096, 15);
+        checkGetPacketLossRate(mTransformStateInitial, 7000, 6000, 4000, 14);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqSmallerThanWinSize_overlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(2000, 1500, newReplayBitmap(1500));
+
+        checkGetPacketLossRate(oldState, 5000, 5001, 4096, 0);
+        checkGetPacketLossRate(oldState, 5000, 4000, 4096, 29);
+        checkGetPacketLossRate(oldState, 5000, 4000, 4000, 27);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqSmallerThanWinSize_notOverlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(2000, 1500, newReplayBitmap(1500));
+
+        checkGetPacketLossRate(oldState, 7000, 7001, 4096, 0);
+        checkGetPacketLossRate(oldState, 7000, 5000, 4096, 37);
+        checkGetPacketLossRate(oldState, 7000, 5000, 3000, 21);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqLargerThanWinSize_overlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(10000, 5000, newReplayBitmap(3000));
+
+        checkGetPacketLossRate(oldState, 12000, 8096, 4096, 0);
+        checkGetPacketLossRate(oldState, 12000, 7000, 4096, 36);
+        checkGetPacketLossRate(oldState, 12000, 7000, 3000, 0);
+    }
+
+    @Test
+    public void testGetPktLossRate_oldHiSeqLargerThanWinSize_notOverlappedWithNewWin()
+            throws Exception {
+        final IpSecTransformState oldState = newTransformState(10000, 5000, newReplayBitmap(3000));
+
+        checkGetPacketLossRate(oldState, 20000, 16096, 4096, 0);
+        checkGetPacketLossRate(oldState, 20000, 14000, 4096, 19);
+        checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10);
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
new file mode 100644
index 0000000..6015e931
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn.routeselection;
+
+import static com.android.server.vcn.VcnTestUtils.setupSystemService;
+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;
+
+import android.content.Context;
+import android.net.IpSecConfig;
+import android.net.IpSecTransform;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.FeatureFlags;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.ParcelUuid;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+import android.telephony.TelephonyManager;
+
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.VcnNetworkProvider;
+
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+import java.util.UUID;
+
+public abstract class NetworkEvaluationTestBase {
+    protected static final String SSID = "TestWifi";
+    protected static final String SSID_OTHER = "TestWifiOther";
+    protected static final String PLMN_ID = "123456";
+    protected static final String PLMN_ID_OTHER = "234567";
+
+    protected static final int SUB_ID = 1;
+    protected static final int WIFI_RSSI = -60;
+    protected static final int WIFI_RSSI_HIGH = -50;
+    protected static final int WIFI_RSSI_LOW = -80;
+    protected static final int CARRIER_ID = 1;
+    protected static final int CARRIER_ID_OTHER = 2;
+
+    protected static final int LINK_UPSTREAM_BANDWIDTH_KBPS = 1024;
+    protected static final int LINK_DOWNSTREAM_BANDWIDTH_KBPS = 2048;
+
+    protected static final int TEST_MIN_UPSTREAM_BANDWIDTH_KBPS = 100;
+    protected static final int TEST_MIN_DOWNSTREAM_BANDWIDTH_KBPS = 200;
+
+    protected static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+
+    protected static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .setSignalStrength(WIFI_RSSI)
+                    .setSsid(SSID)
+                    .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
+                    .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
+                    .build();
+
+    protected static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
+            new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
+    protected static final NetworkCapabilities CELL_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .setSubscriptionIds(Set.of(SUB_ID))
+                    .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
+                    .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
+                    .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
+                    .build();
+
+    protected static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
+
+    @Mock protected Context mContext;
+    @Mock protected Network mNetwork;
+    @Mock protected FeatureFlags mFeatureFlags;
+    @Mock protected com.android.net.flags.FeatureFlags mCoreNetFeatureFlags;
+    @Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
+    @Mock protected TelephonyManager mTelephonyManager;
+    @Mock protected IPowerManager mPowerManagerService;
+
+    protected TestLooper mTestLooper;
+    protected VcnContext mVcnContext;
+    protected PowerManager mPowerManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mNetwork.getNetId()).thenReturn(-1);
+
+        mTestLooper = new TestLooper();
+        mVcnContext =
+                spy(
+                        new VcnContext(
+                                mContext,
+                                mTestLooper.getLooper(),
+                                mock(VcnNetworkProvider.class),
+                                false /* isInTestMode */));
+        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+
+        doReturn(true).when(mVcnContext).isFlagNetworkMetricMonitorEnabled();
+        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
+
+        setupSystemService(
+                mContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
+        when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
+        when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);
+        when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID);
+
+        mPowerManager =
+                new PowerManager(
+                        mContext,
+                        mPowerManagerService,
+                        mock(IThermalService.class),
+                        mock(Handler.class));
+        setupSystemService(mContext, mPowerManager, Context.POWER_SERVICE, PowerManager.class);
+    }
+
+    protected IpSecTransform makeDummyIpSecTransform() throws Exception {
+        return new IpSecTransform(mContext, new IpSecConfig());
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
index 2266041..d85c515 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -24,152 +24,48 @@
 import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS;
 import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS;
 
-import static com.android.server.vcn.VcnTestUtils.setupSystemService;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_FALLBACK;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule;
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule;
-import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
 import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
-import android.net.LinkProperties;
-import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnManager;
 import android.net.vcn.VcnUnderlyingNetworkTemplate;
 import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
-import android.os.ParcelUuid;
 import android.os.PersistableBundle;
-import android.os.test.TestLooper;
-import android.telephony.TelephonyManager;
 import android.util.ArraySet;
 
-import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
-import com.android.server.vcn.VcnContext;
-import com.android.server.vcn.VcnNetworkProvider;
-
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import java.util.UUID;
 
-public class NetworkPriorityClassifierTest {
-    private static final String SSID = "TestWifi";
-    private static final String SSID_OTHER = "TestWifiOther";
-    private static final String PLMN_ID = "123456";
-    private static final String PLMN_ID_OTHER = "234567";
-
-    private static final int SUB_ID = 1;
-    private static final int WIFI_RSSI = -60;
-    private static final int WIFI_RSSI_HIGH = -50;
-    private static final int WIFI_RSSI_LOW = -80;
-    private static final int CARRIER_ID = 1;
-    private static final int CARRIER_ID_OTHER = 2;
-
-    private static final int LINK_UPSTREAM_BANDWIDTH_KBPS = 1024;
-    private static final int LINK_DOWNSTREAM_BANDWIDTH_KBPS = 2048;
-
-    private static final int TEST_MIN_UPSTREAM_BANDWIDTH_KBPS = 100;
-    private static final int TEST_MIN_DOWNSTREAM_BANDWIDTH_KBPS = 200;
-
-    private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
-
-    private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
-            new NetworkCapabilities.Builder()
-                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                    .setSignalStrength(WIFI_RSSI)
-                    .setSsid(SSID)
-                    .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
-                    .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
-                    .build();
-
-    private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
-            new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
-    private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES =
-            new NetworkCapabilities.Builder()
-                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                    .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                    .setSubscriptionIds(Set.of(SUB_ID))
-                    .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
-                    .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS)
-                    .setLinkDownstreamBandwidthKbps(LINK_DOWNSTREAM_BANDWIDTH_KBPS)
-                    .build();
-
-    private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
-
-    @Mock private Network mNetwork;
-    @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
-    @Mock private TelephonyManager mTelephonyManager;
-
-    private TestLooper mTestLooper;
-    private VcnContext mVcnContext;
+public class NetworkPriorityClassifierTest extends NetworkEvaluationTestBase {
     private UnderlyingNetworkRecord mWifiNetworkRecord;
     private UnderlyingNetworkRecord mCellNetworkRecord;
 
     @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
+    public void setUp() throws Exception {
+        super.setUp();
 
-        final Context mockContext = mock(Context.class);
-        mTestLooper = new TestLooper();
-        mVcnContext =
-                spy(
-                        new VcnContext(
-                                mockContext,
-                                mTestLooper.getLooper(),
-                                mock(VcnNetworkProvider.class),
-                                false /* isInTestMode */));
-        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
-
-        setupSystemService(
-                mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
-        when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
-        when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);
-        when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID);
-
-        mWifiNetworkRecord =
-                getTestNetworkRecord(
-                        WIFI_NETWORK_CAPABILITIES,
-                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
-        mCellNetworkRecord =
-                getTestNetworkRecord(
-                        CELL_NETWORK_CAPABILITIES,
-                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES);
+        mWifiNetworkRecord = getTestNetworkRecord(WIFI_NETWORK_CAPABILITIES);
+        mCellNetworkRecord = getTestNetworkRecord(CELL_NETWORK_CAPABILITIES);
     }
 
-    private UnderlyingNetworkRecord getTestNetworkRecord(
-            NetworkCapabilities nc, List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
-        return new UnderlyingNetworkRecord(
-                mNetwork,
-                nc,
-                LINK_PROPERTIES,
-                false /* isBlocked */,
-                mVcnContext,
-                underlyingNetworkTemplates,
-                SUB_GROUP,
-                mSubscriptionSnapshot,
-                null /* currentlySelected */,
-                null /* carrierConfig */);
+    private UnderlyingNetworkRecord getTestNetworkRecord(NetworkCapabilities nc) {
+        return new UnderlyingNetworkRecord(mNetwork, nc, LINK_PROPERTIES, false /* isBlocked */);
     }
 
     @Test
@@ -186,14 +82,14 @@
                         mWifiNetworkRecord,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
-                        null /* currentlySelecetd */,
+                        false /* isSelected */,
                         null /* carrierConfig */));
     }
 
     private void verifyMatchesPriorityRuleForUpstreamBandwidth(
             int entryUpstreamBandwidth,
             int exitUpstreamBandwidth,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             boolean expectMatch) {
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
@@ -208,14 +104,14 @@
                         mWifiNetworkRecord,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
-                        currentlySelected,
+                        isSelected,
                         null /* carrierConfig */));
     }
 
     private void verifyMatchesPriorityRuleForDownstreamBandwidth(
             int entryDownstreamBandwidth,
             int exitDownstreamBandwidth,
-            UnderlyingNetworkRecord currentlySelected,
+            boolean isSelected,
             boolean expectMatch) {
         final VcnWifiUnderlyingNetworkTemplate wifiNetworkPriority =
                 new VcnWifiUnderlyingNetworkTemplate.Builder()
@@ -231,7 +127,7 @@
                         mWifiNetworkRecord,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
-                        currentlySelected,
+                        isSelected,
                         null /* carrierConfig */));
     }
 
@@ -240,7 +136,7 @@
         verifyMatchesPriorityRuleForUpstreamBandwidth(
                 TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS,
                 TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
-                null /* currentlySelected */,
+                false /* isSelected */,
                 true);
     }
 
@@ -249,7 +145,7 @@
         verifyMatchesPriorityRuleForUpstreamBandwidth(
                 LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
                 LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
-                null /* currentlySelected */,
+                false /* isSelected */,
                 false);
     }
 
@@ -258,7 +154,7 @@
         verifyMatchesPriorityRuleForDownstreamBandwidth(
                 TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
                 TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
-                null /* currentlySelected */,
+                false /* isSelected */,
                 true);
     }
 
@@ -267,7 +163,7 @@
         verifyMatchesPriorityRuleForDownstreamBandwidth(
                 LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
                 LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
-                null /* currentlySelected */,
+                false /* isSelected */,
                 false);
     }
 
@@ -276,7 +172,7 @@
         verifyMatchesPriorityRuleForUpstreamBandwidth(
                 TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
                 TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS,
-                mWifiNetworkRecord,
+                true /* isSelected */,
                 true);
     }
 
@@ -285,7 +181,7 @@
         verifyMatchesPriorityRuleForUpstreamBandwidth(
                 LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
                 LINK_UPSTREAM_BANDWIDTH_KBPS + 1,
-                mWifiNetworkRecord,
+                true /* isSelected */,
                 false);
     }
 
@@ -294,7 +190,7 @@
         verifyMatchesPriorityRuleForDownstreamBandwidth(
                 TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
                 TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS,
-                mWifiNetworkRecord,
+                true /* isSelected */,
                 true);
     }
 
@@ -303,7 +199,7 @@
         verifyMatchesPriorityRuleForDownstreamBandwidth(
                 LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
                 LINK_DOWNSTREAM_BANDWIDTH_KBPS + 1,
-                mWifiNetworkRecord,
+                true /* isSelected */,
                 false);
     }
 
@@ -318,14 +214,12 @@
                                 TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS,
                                 TEST_MIN_EXIT_DOWNSTREAM_BANDWIDTH_KBPS)
                         .build();
-        final UnderlyingNetworkRecord selectedNetworkRecord =
-                isSelectedNetwork ? mWifiNetworkRecord : null;
         assertEquals(
                 expectMatch,
                 checkMatchesWifiPriorityRule(
                         wifiNetworkPriority,
                         mWifiNetworkRecord,
-                        selectedNetworkRecord,
+                        isSelectedNetwork,
                         carrierConfig == null
                                 ? null
                                 : new PersistableBundleWrapper(carrierConfig)));
@@ -381,7 +275,7 @@
                 checkMatchesWifiPriorityRule(
                         wifiNetworkPriority,
                         mWifiNetworkRecord,
-                        null /* currentlySelecetd */,
+                        false /* isSelected */,
                         null /* carrierConfig */));
     }
 
@@ -516,7 +410,7 @@
                         mCellNetworkRecord,
                         SUB_GROUP,
                         mSubscriptionSnapshot,
-                        null /* currentlySelected */,
+                        false /* isSelected */,
                         null /* carrierConfig */));
     }
 
@@ -543,7 +437,16 @@
 
     @Test
     public void testCalculatePriorityClass() throws Exception {
-        assertEquals(2, mCellNetworkRecord.priorityClass);
+        final int priorityClass =
+                NetworkPriorityClassifier.calculatePriorityClass(
+                        mVcnContext,
+                        mCellNetworkRecord,
+                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        false /* isSelected */,
+                        null /* carrierConfig */);
+        assertEquals(2, priorityClass);
     }
 
     private void checkCalculatePriorityClassFailToMatchAny(
@@ -561,10 +464,19 @@
             ncBuilder.addCapability(NET_CAPABILITY_INTERNET);
         }
 
-        final UnderlyingNetworkRecord nonDunNetworkRecord =
-                getTestNetworkRecord(ncBuilder.build(), templatesRequireDun);
+        final UnderlyingNetworkRecord nonDunNetworkRecord = getTestNetworkRecord(ncBuilder.build());
 
-        assertEquals(expectedPriorityClass, nonDunNetworkRecord.priorityClass);
+        final int priorityClass =
+                NetworkPriorityClassifier.calculatePriorityClass(
+                        mVcnContext,
+                        nonDunNetworkRecord,
+                        templatesRequireDun,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        false /* isSelected */,
+                        null /* carrierConfig */);
+
+        assertEquals(expectedPriorityClass, priorityClass);
     }
 
     @Test
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 2941fde..588624b 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -29,13 +29,12 @@
 import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -48,6 +47,8 @@
 
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.IpSecConfig;
+import android.net.IpSecTransform;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -67,9 +68,11 @@
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.VcnContext;
 import com.android.server.vcn.VcnNetworkProvider;
+import com.android.server.vcn.routeselection.UnderlyingNetworkController.Dependencies;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback;
 import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -77,6 +80,7 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -152,12 +156,17 @@
     @Mock private CarrierConfigManager mCarrierConfigManager;
     @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
     @Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb;
+    @Mock private NetworkEvaluatorCallback mEvaluatorCallback;
     @Mock private Network mNetwork;
 
+    @Spy private Dependencies mDependencies = new Dependencies();
+
     @Captor private ArgumentCaptor<UnderlyingNetworkListener> mUnderlyingNetworkListenerCaptor;
+    @Captor private ArgumentCaptor<NetworkEvaluatorCallback> mEvaluatorCallbackCaptor;
 
     private TestLooper mTestLooper;
     private VcnContext mVcnContext;
+    private UnderlyingNetworkEvaluator mNetworkEvaluator;
     private UnderlyingNetworkController mUnderlyingNetworkController;
 
     @Before
@@ -172,7 +181,7 @@
                                 mTestLooper.getLooper(),
                                 mVcnNetworkProvider,
                                 false /* isInTestMode */));
-        resetVcnContext();
+        resetVcnContext(mVcnContext);
 
         setupSystemService(
                 mContext,
@@ -189,18 +198,36 @@
 
         when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS);
 
+        mNetworkEvaluator =
+                spy(
+                        new UnderlyingNetworkEvaluator(
+                                mVcnContext,
+                                mNetwork,
+                                VcnGatewayConnectionConfigTest.buildTestConfig()
+                                        .getVcnUnderlyingNetworkPriorities(),
+                                SUB_GROUP,
+                                mSubscriptionSnapshot,
+                                null,
+                                mEvaluatorCallback));
+        doReturn(mNetworkEvaluator)
+                .when(mDependencies)
+                .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
+
         mUnderlyingNetworkController =
                 new UnderlyingNetworkController(
                         mVcnContext,
                         VcnGatewayConnectionConfigTest.buildTestConfig(),
                         SUB_GROUP,
                         mSubscriptionSnapshot,
-                        mNetworkControllerCb);
+                        mNetworkControllerCb,
+                        mDependencies);
     }
 
-    private void resetVcnContext() {
-        reset(mVcnContext);
-        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+    private void resetVcnContext(VcnContext vcnContext) {
+        reset(vcnContext);
+        doNothing().when(vcnContext).ensureRunningOnLooperThread();
+        doReturn(true).when(vcnContext).isFlagNetworkMetricMonitorEnabled();
+        doReturn(true).when(vcnContext).isFlagIpSecTransformStateEnabled();
     }
 
     // Package private for use in NetworkPriorityClassifierTest
@@ -226,11 +253,13 @@
         final ConnectivityManager cm = mock(ConnectivityManager.class);
         setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
         final VcnContext vcnContext =
-                new VcnContext(
-                        mContext,
-                        mTestLooper.getLooper(),
-                        mVcnNetworkProvider,
-                        true /* isInTestMode */);
+                spy(
+                        new VcnContext(
+                                mContext,
+                                mTestLooper.getLooper(),
+                                mVcnNetworkProvider,
+                                true /* isInTestMode */));
+        resetVcnContext(vcnContext);
 
         new UnderlyingNetworkController(
                 vcnContext,
@@ -489,13 +518,7 @@
             NetworkCapabilities networkCapabilities,
             LinkProperties linkProperties,
             boolean isBlocked) {
-        return new UnderlyingNetworkRecord(
-                network,
-                networkCapabilities,
-                linkProperties,
-                isBlocked,
-                false /* isSelected */,
-                0 /* priorityClass */);
+        return new UnderlyingNetworkRecord(network, networkCapabilities, linkProperties, isBlocked);
     }
 
     @Test
@@ -515,24 +538,12 @@
         UnderlyingNetworkRecord recordC =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        INITIAL_NETWORK_CAPABILITIES,
-                        INITIAL_LINK_PROPERTIES,
-                        false /* isBlocked */,
-                        true /* isSelected */,
-                        -1 /* priorityClass */);
-        UnderlyingNetworkRecord recordD =
-                getTestNetworkRecord(
-                        mNetwork,
                         UPDATED_NETWORK_CAPABILITIES,
                         UPDATED_LINK_PROPERTIES,
                         false /* isBlocked */);
 
         assertEquals(recordA, recordB);
-        assertEquals(recordA, recordC);
-        assertNotEquals(recordA, recordD);
-
-        assertTrue(UnderlyingNetworkRecord.isEqualIncludingPriorities(recordA, recordB));
-        assertFalse(UnderlyingNetworkRecord.isEqualIncludingPriorities(recordA, recordC));
+        assertNotEquals(recordA, recordC);
     }
 
     @Test
@@ -540,6 +551,58 @@
         verifyRegistrationOnAvailableAndGetCallback();
     }
 
+    @Test
+    public void testUpdateSubscriptionSnapshotAndCarrierConfig() {
+        verifyRegistrationOnAvailableAndGetCallback();
+
+        TelephonySubscriptionSnapshot subscriptionUpdate =
+                mock(TelephonySubscriptionSnapshot.class);
+        when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS);
+
+        mUnderlyingNetworkController.updateSubscriptionSnapshot(subscriptionUpdate);
+
+        verify(mNetworkEvaluator).reevaluate(any(), any(), any(), any());
+    }
+
+    @Test
+    public void testUpdateIpSecTransform() {
+        verifyRegistrationOnAvailableAndGetCallback();
+
+        final UnderlyingNetworkRecord expectedRecord =
+                getTestNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        final IpSecTransform expectedTransform = new IpSecTransform(mContext, new IpSecConfig());
+
+        mUnderlyingNetworkController.updateInboundTransform(expectedRecord, expectedTransform);
+        verify(mNetworkEvaluator).setInboundTransform(expectedTransform);
+    }
+
+    @Test
+    public void testOnEvaluationResultChanged() {
+        verifyRegistrationOnAvailableAndGetCallback();
+
+        // Verify #reevaluateNetworks is called by checking #getNetworkRecord
+        verify(mNetworkEvaluator).getNetworkRecord();
+
+        // Trigger the callback
+        verify(mDependencies)
+                .newUnderlyingNetworkEvaluator(
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        mEvaluatorCallbackCaptor.capture());
+        mEvaluatorCallbackCaptor.getValue().onEvaluationResultChanged();
+
+        // Verify #reevaluateNetworks is called again
+        verify(mNetworkEvaluator, times(2)).getNetworkRecord();
+    }
+
     private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback() {
         return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
     }
@@ -583,6 +646,7 @@
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         verifyOnSelectedUnderlyingNetworkChanged(expectedRecord);
+        verify(mNetworkEvaluator).setIsSelected(eq(true), any(), any(), any(), any());
         return cb;
     }
 
@@ -667,7 +731,7 @@
 
         cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
 
-        verifyOnSelectedUnderlyingNetworkChanged(null);
+        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
     }
 
     @Test
@@ -675,6 +739,7 @@
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
 
         cb.onLost(mNetwork);
+        verify(mNetworkEvaluator).close();
 
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
     }
@@ -713,7 +778,8 @@
                 VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates),
                 SUB_GROUP,
                 mSubscriptionSnapshot,
-                mNetworkControllerCb);
+                mNetworkControllerCb,
+                mDependencies);
 
         verify(cm)
                 .registerNetworkCallback(
@@ -724,30 +790,44 @@
         return mUnderlyingNetworkListenerCaptor.getValue();
     }
 
-    private UnderlyingNetworkRecord bringupNetworkAndGetRecord(
+    private UnderlyingNetworkEvaluator bringupNetworkAndGetEvaluator(
             UnderlyingNetworkListener cb,
             NetworkCapabilities requestNetworkCaps,
-            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
-            UnderlyingNetworkRecord currentlySelected) {
+            List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) {
         final Network network = mock(Network.class);
         final NetworkCapabilities responseNetworkCaps =
                 buildResponseNwCaps(requestNetworkCaps, INITIAL_SUB_IDS);
+        final UnderlyingNetworkEvaluator evaluator =
+                spy(
+                        new UnderlyingNetworkEvaluator(
+                                mVcnContext,
+                                network,
+                                underlyingNetworkTemplates,
+                                SUB_GROUP,
+                                mSubscriptionSnapshot,
+                                null,
+                                mEvaluatorCallback));
+        doReturn(evaluator)
+                .when(mDependencies)
+                .newUnderlyingNetworkEvaluator(any(), any(), any(), any(), any(), any(), any());
 
         cb.onAvailable(network);
         cb.onCapabilitiesChanged(network, responseNetworkCaps);
         cb.onLinkPropertiesChanged(network, INITIAL_LINK_PROPERTIES);
         cb.onBlockedStatusChanged(network, false /* isFalse */);
-        return new UnderlyingNetworkRecord(
-                network,
-                responseNetworkCaps,
-                INITIAL_LINK_PROPERTIES,
-                false /* isBlocked */,
-                mVcnContext,
-                underlyingNetworkTemplates,
-                SUB_GROUP,
-                mSubscriptionSnapshot,
-                currentlySelected,
-                null /* carrierConfig */);
+
+        return evaluator;
+    }
+
+    private void verifySelectNetwork(UnderlyingNetworkEvaluator expectedEvaluator) {
+        verifyOnSelectedUnderlyingNetworkChanged(expectedEvaluator.getNetworkRecord());
+        verify(expectedEvaluator).setIsSelected(eq(true), any(), any(), any(), any());
+    }
+
+    private void verifyNeverSelectNetwork(UnderlyingNetworkEvaluator expectedEvaluator) {
+        verify(mNetworkControllerCb, never())
+                .onSelectedUnderlyingNetworkChanged(eq(expectedEvaluator.getNetworkRecord()));
+        verify(expectedEvaluator, never()).setIsSelected(eq(true), any(), any(), any(), any());
     }
 
     @Test
@@ -759,19 +839,15 @@
         UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
 
         // Bring up CBS network
-        final UnderlyingNetworkRecord cbsNetworkRecord =
-                bringupNetworkAndGetRecord(
-                        cb,
-                        CBS_NETWORK_CAPABILITIES,
-                        networkTemplates,
-                        null /* currentlySelected */);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord));
+        final UnderlyingNetworkEvaluator cbsNetworkEvaluator =
+                bringupNetworkAndGetEvaluator(cb, CBS_NETWORK_CAPABILITIES, networkTemplates);
+        verifySelectNetwork(cbsNetworkEvaluator);
 
         // Bring up DUN network
-        final UnderlyingNetworkRecord dunNetworkRecord =
-                bringupNetworkAndGetRecord(
-                        cb, DUN_NETWORK_CAPABILITIES, networkTemplates, cbsNetworkRecord);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord));
+        final UnderlyingNetworkEvaluator dunNetworkEvaluator =
+                bringupNetworkAndGetEvaluator(cb, DUN_NETWORK_CAPABILITIES, networkTemplates);
+        verifySelectNetwork(dunNetworkEvaluator);
+        verify(cbsNetworkEvaluator).setIsSelected(eq(false), any(), any(), any(), any());
     }
 
     @Test
@@ -783,20 +859,14 @@
         UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
 
         // Bring up DUN network
-        final UnderlyingNetworkRecord dunNetworkRecord =
-                bringupNetworkAndGetRecord(
-                        cb,
-                        DUN_NETWORK_CAPABILITIES,
-                        networkTemplates,
-                        null /* currentlySelected */);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord));
+        final UnderlyingNetworkEvaluator dunNetworkEvaluator =
+                bringupNetworkAndGetEvaluator(cb, DUN_NETWORK_CAPABILITIES, networkTemplates);
+        verifySelectNetwork(dunNetworkEvaluator);
 
         // Bring up CBS network
-        final UnderlyingNetworkRecord cbsNetworkRecord =
-                bringupNetworkAndGetRecord(
-                        cb, CBS_NETWORK_CAPABILITIES, networkTemplates, dunNetworkRecord);
-        verify(mNetworkControllerCb, never())
-                .onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord));
+        final UnderlyingNetworkEvaluator cbsNetworkEvaluator =
+                bringupNetworkAndGetEvaluator(cb, CBS_NETWORK_CAPABILITIES, networkTemplates);
+        verifyNeverSelectNetwork(cbsNetworkEvaluator);
     }
 
     @Test
@@ -808,13 +878,9 @@
         UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
 
         // Bring up an Internet network without DUN capability
-        final UnderlyingNetworkRecord networkRecord =
-                bringupNetworkAndGetRecord(
-                        cb,
-                        INITIAL_NETWORK_CAPABILITIES,
-                        networkTemplates,
-                        null /* currentlySelected */);
-        verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(networkRecord));
+        final UnderlyingNetworkEvaluator evaluator =
+                bringupNetworkAndGetEvaluator(cb, INITIAL_NETWORK_CAPABILITIES, networkTemplates);
+        verifySelectNetwork(evaluator);
     }
 
     @Test
@@ -825,10 +891,8 @@
                 new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build());
         UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates);
 
-        bringupNetworkAndGetRecord(
-                cb, CBS_NETWORK_CAPABILITIES, networkTemplates, null /* currentlySelected */);
-
-        verify(mNetworkControllerCb, never())
-                .onSelectedUnderlyingNetworkChanged(any(UnderlyingNetworkRecord.class));
+        final UnderlyingNetworkEvaluator evaluator =
+                bringupNetworkAndGetEvaluator(cb, CBS_NETWORK_CAPABILITIES, networkTemplates);
+        verifyNeverSelectNetwork(evaluator);
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
new file mode 100644
index 0000000..aa81efe
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn.routeselection;
+
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY;
+
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID;
+import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.IpSecTransform;
+import android.net.vcn.VcnGatewayConnectionConfig;
+
+import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.Dependencies;
+import com.android.server.vcn.routeselection.UnderlyingNetworkEvaluator.NetworkEvaluatorCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+import java.util.concurrent.TimeUnit;
+
+public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase {
+    private static final int PENALTY_TIMEOUT_MIN = 10;
+    private static final long PENALTY_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(PENALTY_TIMEOUT_MIN);
+
+    @Mock private PersistableBundleWrapper mCarrierConfig;
+    @Mock private IpSecPacketLossDetector mIpSecPacketLossDetector;
+    @Mock private Dependencies mDependencies;
+    @Mock private NetworkEvaluatorCallback mEvaluatorCallback;
+
+    @Captor private ArgumentCaptor<NetworkMetricMonitorCallback> mMetricMonitorCbCaptor;
+
+    private UnderlyingNetworkEvaluator mNetworkEvaluator;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        when(mDependencies.newIpSecPacketLossDetector(any(), any(), any(), any()))
+                .thenReturn(mIpSecPacketLossDetector);
+
+        when(mCarrierConfig.getIntArray(
+                        eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+                .thenReturn(new int[] {PENALTY_TIMEOUT_MIN});
+
+        mNetworkEvaluator = newValidUnderlyingNetworkEvaluator();
+    }
+
+    private UnderlyingNetworkEvaluator newUnderlyingNetworkEvaluator() {
+        return new UnderlyingNetworkEvaluator(
+                mVcnContext,
+                mNetwork,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig,
+                mEvaluatorCallback,
+                mDependencies);
+    }
+
+    private UnderlyingNetworkEvaluator newValidUnderlyingNetworkEvaluator() {
+        final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+
+        evaluator.setNetworkCapabilities(
+                CELL_NETWORK_CAPABILITIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        evaluator.setLinkProperties(
+                LINK_PROPERTIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        evaluator.setIsBlocked(
+                false /* isBlocked */,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+
+        return evaluator;
+    }
+
+    @Test
+    public void testInitializedEvaluator() throws Exception {
+        final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+
+        assertFalse(evaluator.isValid());
+        assertEquals(mNetwork, evaluator.getNetwork());
+        assertEquals(PRIORITY_INVALID, evaluator.getPriorityClass());
+
+        try {
+            evaluator.getNetworkRecord();
+            fail("Expected to fail because evaluator is not valid");
+        } catch (Exception expected) {
+        }
+    }
+
+    @Test
+    public void testValidEvaluator() {
+        final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator();
+        evaluator.setNetworkCapabilities(
+                CELL_NETWORK_CAPABILITIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        evaluator.setLinkProperties(
+                LINK_PROPERTIES,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        evaluator.setIsBlocked(
+                false /* isBlocked */,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+
+        final UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        CELL_NETWORK_CAPABILITIES,
+                        LINK_PROPERTIES,
+                        false /* isBlocked */);
+
+        assertTrue(evaluator.isValid());
+        assertEquals(mNetwork, evaluator.getNetwork());
+        assertEquals(2, evaluator.getPriorityClass());
+        assertEquals(expectedRecord, evaluator.getNetworkRecord());
+    }
+
+    private void checkSetSelectedNetwork(boolean isSelected) {
+        mNetworkEvaluator.setIsSelected(
+                isSelected,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        verify(mIpSecPacketLossDetector).setIsSelectedUnderlyingNetwork(isSelected);
+    }
+
+    @Test
+    public void testSetIsSelected_selected() throws Exception {
+        checkSetSelectedNetwork(true /* isSelectedExpected */);
+    }
+
+    @Test
+    public void testSetIsSelected_unselected() throws Exception {
+        checkSetSelectedNetwork(false /* isSelectedExpected */);
+    }
+
+    @Test
+    public void testSetIpSecTransform_onSelectedNetwork() throws Exception {
+        final IpSecTransform transform = makeDummyIpSecTransform();
+
+        // Make the network selected
+        mNetworkEvaluator.setIsSelected(
+                true,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        mNetworkEvaluator.setInboundTransform(transform);
+
+        verify(mIpSecPacketLossDetector).setInboundTransform(transform);
+    }
+
+    @Test
+    public void testSetIpSecTransform_onUnSelectedNetwork() throws Exception {
+        mNetworkEvaluator.setIsSelected(
+                false,
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        mNetworkEvaluator.setInboundTransform(makeDummyIpSecTransform());
+
+        verify(mIpSecPacketLossDetector, never()).setInboundTransform(any());
+    }
+
+    @Test
+    public void close() throws Exception {
+        mNetworkEvaluator.close();
+
+        verify(mIpSecPacketLossDetector).close();
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    private NetworkMetricMonitorCallback getMetricMonitorCbCaptor() throws Exception {
+        verify(mDependencies)
+                .newIpSecPacketLossDetector(any(), any(), any(), mMetricMonitorCbCaptor.capture());
+
+        return mMetricMonitorCbCaptor.getValue();
+    }
+
+    private void checkPenalizeNetwork() throws Exception {
+        assertFalse(mNetworkEvaluator.isPenalized());
+
+        // Validation failed
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        // Verify the evaluator is penalized
+        assertTrue(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback).onEvaluationResultChanged();
+    }
+
+    @Test
+    public void testRcvValidationResult_penalizeNetwork_penaltyTimeout() throws Exception {
+        checkPenalizeNetwork();
+
+        // Penalty timeout
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        // Verify the evaluator is not penalized
+        assertFalse(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
+    }
+
+    @Test
+    public void testRcvValidationResult_penalizeNetwork_passValidation() throws Exception {
+        checkPenalizeNetwork();
+
+        // Validation passed
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        // Verify the evaluator is not penalized and penalty timeout is canceled
+        assertFalse(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback, times(2)).onEvaluationResultChanged();
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testRcvValidationResult_penalizeNetwork_closeEvaluator() throws Exception {
+        checkPenalizeNetwork();
+
+        mNetworkEvaluator.close();
+
+        // Verify penalty timeout is canceled
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+    }
+
+    @Test
+    public void testRcvValidationResult_PenaltyStateUnchanged() throws Exception {
+        assertFalse(mNetworkEvaluator.isPenalized());
+
+        // Validation passed
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(false);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        // Verifications
+        assertFalse(mNetworkEvaluator.isPenalized());
+        verify(mEvaluatorCallback, never()).onEvaluationResultChanged();
+    }
+
+    @Test
+    public void testSetCarrierConfig() throws Exception {
+        final int additionalTimeoutMin = 10;
+        when(mCarrierConfig.getIntArray(
+                        eq(VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY), anyObject()))
+                .thenReturn(new int[] {PENALTY_TIMEOUT_MIN + additionalTimeoutMin});
+
+        // Update evaluator and penalize the network
+        mNetworkEvaluator.reevaluate(
+                VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES,
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mCarrierConfig);
+        checkPenalizeNetwork();
+
+        // Verify penalty timeout is changed
+        mTestLooper.moveTimeForward(PENALTY_TIMEOUT_MS);
+        assertNull(mTestLooper.nextMessage());
+        mTestLooper.moveTimeForward(TimeUnit.MINUTES.toMillis(additionalTimeoutMin));
+        assertNotNull(mTestLooper.nextMessage());
+
+        // Verify NetworkMetricMonitor is notified
+        verify(mIpSecPacketLossDetector).setCarrierConfig(any());
+    }
+
+    @Test
+    public void testCompare() throws Exception {
+        when(mIpSecPacketLossDetector.isValidationFailed()).thenReturn(true);
+        getMetricMonitorCbCaptor().onValidationResultReceived();
+
+        final UnderlyingNetworkEvaluator penalized = mNetworkEvaluator;
+        final UnderlyingNetworkEvaluator notPenalized = newValidUnderlyingNetworkEvaluator();
+
+        assertEquals(penalized.getPriorityClass(), notPenalized.getPriorityClass());
+
+        final int result =
+                UnderlyingNetworkEvaluator.getComparator(mVcnContext)
+                        .compare(penalized, notPenalized);
+        assertEquals(1, result);
+    }
+}
diff --git a/tools/aapt/ApkBuilder.h b/tools/aapt/ApkBuilder.h
index 5d3abc6..9276402 100644
--- a/tools/aapt/ApkBuilder.h
+++ b/tools/aapt/ApkBuilder.h
@@ -44,7 +44,7 @@
     android::status_t createSplitForConfigs(const std::set<ConfigDescription>& configs);
 
     /**
-     * Adds a file to be written to the final APK. It's name must not collide
+     * Adds a file to be written to the final APK. Its name must not collide
      * with that of any files previously added. When a Split APK is being
      * generated, duplicates can exist as long as they are in different splits
      * (resources.arsc, AndroidManifest.xml).
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 2f2ef92..66a0510 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -187,7 +187,7 @@
         "       be loaded alongside the base APK at runtime.\n"
         "   --feature-of\n"
         "       Builds a split APK that is a feature of the apk specified here. Resources\n"
-        "       in the base APK can be referenced from the the feature APK.\n"
+        "       in the base APK can be referenced from the feature APK.\n"
         "   --feature-after\n"
         "       An app can have multiple Feature Split APKs which must be totally ordered.\n"
         "       If --feature-of is specified, this flag specifies which Feature Split APK\n"
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 40cba3e..dbac6ca 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -236,3 +236,56 @@
         ],
     },
 }
+
+cc_genrule {
+    name: "aapt2_results",
+    srcs: [
+        ":aapt2_tests",
+        "integration-tests/CompileTest/**/*",
+        "integration-tests/CommandTests/**/*",
+        "integration-tests/ConvertTest/**/*",
+        "integration-tests/DumpTest/**/*",
+    ],
+    host_supported: true,
+    device_supported: false,
+    target: {
+        windows: {
+            compile_multilib: "64",
+        },
+    },
+    out: ["result.xml"],
+    cmd: "mkdir -p $(genDir)/integration-tests/CompileTest/ && " +
+        "cp $(locations integration-tests/CompileTest/**/*) $(genDir)/integration-tests/CompileTest/ && " +
+        "mkdir -p $(genDir)/integration-tests/CommandTests/ && " +
+        "cp $(locations integration-tests/CommandTests/**/*) $(genDir)/integration-tests/CompileTest/ && " +
+        "mkdir -p $(genDir)/integration-tests/ConvertTest/ && " +
+        "cp $(locations integration-tests/ConvertTest/**/*) $(genDir)/integration-tests/ConvertTest/ && " +
+        "mkdir -p $(genDir)/integration-tests/DumpTest/ && " +
+        "cp $(locations integration-tests/DumpTest/**/*) $(genDir)/integration-tests/DumpTest/ && " +
+        "cp $(locations :aapt2_tests) $(genDir)/ && " +
+        "$(genDir)/aapt2_tests " +
+        "--gtest_output=xml:$(out) " +
+        ">/dev/null 2>&1 ; true",
+    dist: {
+        targets: ["aapt2_run_host_unit_tests"],
+        dir: "gtest",
+        dest: "aapt2_host_unit_tests_result.xml",
+    },
+    arch: {
+        x86: {
+            dist: {
+                suffix: "_x86",
+            },
+        },
+        x86_64: {
+            dist: {
+                suffix: "_x86_64",
+            },
+        },
+    },
+}
+
+phony_rule {
+    name: "aapt2_run_host_unit_tests",
+    phony_deps: ["aapt2_results"],
+}
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 34a1b11..15ae2ba 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -1,22 +1,4 @@
-LOCAL_PATH := $(call my-dir)
-
 include $(CLEAR_VARS)
-
-aapt2_results := $(call intermediates-dir-for,PACKAGING,aapt2_run_host_unit_tests)/result.xml
-
-# Target for running host unit tests on post/pre-submit.
-.PHONY: aapt2_run_host_unit_tests
-aapt2_run_host_unit_tests: $(aapt2_results)
-
-$(call dist-for-goals,aapt2_run_host_unit_tests,$(aapt2_results):gtest/aapt2_host_unit_tests_result.xml)
-
-# Always run the tests again, even if they haven't changed
-$(aapt2_results): .KATI_IMPLICIT_OUTPUTS := $(aapt2_results)-nocache
-$(aapt2_results): $(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests
-	-$(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests --gtest_output=xml:$@ > /dev/null 2>&1
-
+aapt2_results := ./out/soong/.intermediates/frameworks/base/tools/aapt2/aapt2_results
 $(call declare-1p-target,$(aapt2_results))
-
 aapt2_results :=
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index cf4dd79..c739d77 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -56,6 +56,7 @@
 #include "java/JavaClassGenerator.h"
 #include "java/ManifestClassGenerator.h"
 #include "java/ProguardRules.h"
+#include "link/FeatureFlagsFilter.h"
 #include "link/Linkers.h"
 #include "link/ManifestFixer.h"
 #include "link/NoDefaultResourceRemover.h"
@@ -1986,6 +1987,21 @@
     context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()});
     context_->SetSplitNameDependencies(app_info_.split_name_dependencies);
 
+    std::unique_ptr<xml::XmlResource> pre_flags_filter_manifest_xml = manifest_xml->Clone();
+
+    FeatureFlagsFilterOptions flags_filter_options;
+    if (context_->GetMinSdkVersion() > SDK_UPSIDE_DOWN_CAKE) {
+      // For API version > U, PackageManager will dynamically read the flag values and disable
+      // manifest elements accordingly when parsing the manifest.
+      // For API version <= U, we remove disabled elements from the manifest with the filter.
+      flags_filter_options.remove_disabled_elements = false;
+      flags_filter_options.flags_must_have_value = false;
+    }
+    FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
+    if (!flags_filter.Consume(context_, manifest_xml.get())) {
+      return 1;
+    }
+
     // Override the package ID when it is "android".
     if (context_->GetCompilationPackage() == "android") {
       context_->SetPackageId(kAndroidPackageId);
@@ -2282,7 +2298,12 @@
         }
 
         if (options_.generate_java_class_path) {
-          if (!WriteManifestJavaFile(manifest_xml.get())) {
+          // The FeatureFlagsFilter may remove <permission> and <permission-group> elements that
+          // generate constants in the Manifest Java file. While we want those permissions and
+          // permission groups removed in the SDK (i.e., if a feature flag is disabled), the
+          // constants should still remain so that code referencing it (e.g., within a feature
+          // flag check) will still compile. Therefore we use the manifest XML before the filter.
+          if (!WriteManifestJavaFile(pre_flags_filter_manifest_xml.get())) {
             error = true;
           }
         }
@@ -2530,7 +2551,7 @@
   }
 
   for (const std::string& arg : all_feature_flags_args) {
-    if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
+    if (!ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
       return 1;
     }
   }
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 26713fd..dc18b1c 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -330,7 +330,11 @@
             "should only be used together with the --static-lib flag.",
         &options_.merge_only);
     AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
-    AddOptionalFlagList("--feature-flags", "Placeholder, to be implemented.", &feature_flags_args_);
+    AddOptionalFlagList("--feature-flags",
+                        "Specify the values of feature flags. The pairs in the argument\n"
+                        "are separated by ',' and the name is separated from the value by '='.\n"
+                        "Example: \"flag1=true,flag2=false,flag3=\" (flag3 has no given value).",
+                        &feature_flags_args_);
   }
 
   int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 725a1b8..10d0b1f 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -16,11 +16,10 @@
 
 #include "Link.h"
 
-#include <android-base/file.h>
-
-#include "AppInfo.h"
 #include "Diagnostics.h"
 #include "LoadedApk.h"
+#include "android-base/file.h"
+#include "android-base/stringprintf.h"
 #include "test/Test.h"
 
 using testing::Eq;
@@ -993,4 +992,221 @@
   ASSERT_FALSE(Link(link_args, &diag));
 }
 
+static void BuildSDKWithFeatureFlagAttr(const std::string& apk_path, const std::string& java_path,
+                                        CommandTestFixture* fixture, android::IDiagnostics* diag) {
+  const std::string android_values =
+      R"(<resources>
+          <staging-public-group type="attr" first-id="0x01fe0063">
+            <public name="featureFlag" />
+          </staging-public-group>
+          <attr name="featureFlag" format="string" />
+         </resources>)";
+
+  SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
+  BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
+}
+
+TEST_F(LinkTest, FeatureFlagDisabled_SdkAtMostUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_UPSIDE_DOWN_CAKE);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  const std::string app_java = GetTestPath("app-java");
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--java", app_java)
+                           .AddParameter("--feature-flags", "flag=false");
+
+  const std::string app_apk = GetTestPath("app.apk");
+  BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+  // Permission element should be removed if flag is disabled
+  auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+  ASSERT_THAT(apk, NotNull());
+  auto apk_manifest = apk->GetManifest();
+  ASSERT_THAT(apk_manifest, NotNull());
+  auto root = apk_manifest->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, IsNull());
+
+  // Code for the permission should be generated even if the element is removed
+  const std::string manifest_java = app_java + "/com/example/app/Manifest.java";
+  std::string manifest_java_contents;
+  ASSERT_TRUE(android::base::ReadFileToString(manifest_java, &manifest_java_contents));
+  EXPECT_THAT(manifest_java_contents, HasSubstr(" public static final String FOO=\"FOO\";"));
+}
+
+TEST_F(LinkTest, FeatureFlagEnabled_SdkAtMostUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_UPSIDE_DOWN_CAKE);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--feature-flags", "flag=true");
+
+  const std::string app_apk = GetTestPath("app.apk");
+  BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+  // Permission element should be kept if flag is enabled
+  auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+  ASSERT_THAT(apk, NotNull());
+  auto apk_manifest = apk->GetManifest();
+  ASSERT_THAT(apk_manifest, NotNull());
+  auto root = apk_manifest->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAtMostUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_UPSIDE_DOWN_CAKE);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--feature-flags", "flag=");
+
+  // Flags must have values if <= UDC
+  const std::string app_apk = GetTestPath("app.apk");
+  ASSERT_FALSE(Link(app_link_args.Build(app_apk), &diag));
+}
+
+TEST_F(LinkTest, FeatureFlagDisabled_SdkAfterUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_CUR_DEVELOPMENT);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--feature-flags", "flag=false");
+
+  const std::string app_apk = GetTestPath("app.apk");
+  BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+  // Permission element should be kept if > UDC, regardless of flag value
+  auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+  ASSERT_THAT(apk, NotNull());
+  auto apk_manifest = apk->GetManifest();
+  ASSERT_THAT(apk_manifest, NotNull());
+  auto root = apk_manifest->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagEnabled_SdkAfterUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_CUR_DEVELOPMENT);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--feature-flags", "flag=true");
+
+  const std::string app_apk = GetTestPath("app.apk");
+  BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+  // Permission element should be kept if > UDC, regardless of flag value
+  auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+  ASSERT_THAT(apk, NotNull());
+  auto apk_manifest = apk->GetManifest();
+  ASSERT_THAT(apk_manifest, NotNull());
+  auto root = apk_manifest->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAfterUDC) {
+  StdErrDiagnostics diag;
+  const std::string android_apk = GetTestPath("android.apk");
+  const std::string android_java = GetTestPath("android-java");
+  BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag);
+
+  const std::string manifest_contents = android::base::StringPrintf(
+      R"(<uses-sdk android:minSdkVersion="%d" />"
+          <permission android:name="FOO" android:featureFlag="flag" />)",
+      SDK_CUR_DEVELOPMENT);
+  auto app_manifest = ManifestBuilder(this)
+                          .SetPackageName("com.example.app")
+                          .AddContents(manifest_contents)
+                          .Build();
+
+  auto app_link_args = LinkCommandBuilder(this)
+                           .SetManifestFile(app_manifest)
+                           .AddParameter("-I", android_apk)
+                           .AddParameter("--feature-flags", "flag=");
+
+  const std::string app_apk = GetTestPath("app.apk");
+  BuildApk({}, app_apk, std::move(app_link_args), this, &diag);
+
+  // Permission element should be kept if > UDC, regardless of flag value
+  auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag);
+  ASSERT_THAT(apk, NotNull());
+  auto apk_manifest = apk->GetManifest();
+  ASSERT_THAT(apk_manifest, NotNull());
+  auto root = apk_manifest->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
deleted file mode 100644
index 6361f9b..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
deleted file mode 100644
index 27b6068..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk
+++ /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.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestMergeOnly_App
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    AaptTestMergeOnly_LeafLib \
-    AaptTestMergeOnly_LocalLib
-include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
deleted file mode 100644
index c084849..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk
+++ /dev/null
@@ -1,31 +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.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestMergeOnly_LeafLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-LOCAL_AAPT_FLAGS := --merge-only
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
deleted file mode 100644
index 699ad79..0000000
--- a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk
+++ /dev/null
@@ -1,31 +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.
-//
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestMergeOnly_LocalLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-LOCAL_AAPT_FLAGS := --merge-only
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Android.mk
deleted file mode 100644
index 6361f9b..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/Android.mk
+++ /dev/null
@@ -1,2 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
deleted file mode 100644
index 98b7440..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestNamespace_App
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    AaptTestNamespace_LibOne \
-    AaptTestNamespace_LibTwo
-include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
deleted file mode 100644
index dd41702..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestNamespace_LibOne
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-
-# We need this to retain the R.java generated for this library.
-LOCAL_JAR_EXCLUDE_FILES := none
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
deleted file mode 100644
index 0d11bcb..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_MODULE := AaptTestNamespace_LibTwo
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_MIN_SDK_VERSION := 21
-
-# We need this to retain the R.java generated for this library.
-LOCAL_JAR_EXCLUDE_FILES := none
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk
deleted file mode 100644
index 30375728..0000000
--- a/tools/aapt2/integration-tests/NamespaceTest/Split/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_NAMESPACES := true
-LOCAL_PACKAGE_NAME := AaptTestNamespace_Split
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE  := $(LOCAL_PATH)/../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_APK_LIBRARIES := AaptTestNamespace_App
-LOCAL_RES_LIBRARIES := AaptTestNamespace_App
-LOCAL_AAPT_FLAGS := --package-id 0x80 --rename-manifest-package com.android.aapt.namespace.app
-include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 93c1b61..02e4beae 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -251,10 +251,13 @@
     return false;
   }
 
-  for (StringPiece line : util::Tokenize(contents, ' ')) {
+  for (StringPiece line : util::Tokenize(contents, '\n')) {
     line = util::TrimWhitespace(line);
-    if (!line.empty()) {
-      out_arglist->emplace_back(line);
+    for (StringPiece arg : util::Tokenize(line, ' ')) {
+      arg = util::TrimWhitespace(arg);
+      if (!arg.empty()) {
+        out_arglist->emplace_back(arg);
+      }
     }
   }
   return true;
@@ -270,10 +273,13 @@
     return false;
   }
 
-  for (StringPiece line : util::Tokenize(contents, ' ')) {
+  for (StringPiece line : util::Tokenize(contents, '\n')) {
     line = util::TrimWhitespace(line);
-    if (!line.empty()) {
-      out_argset->emplace(line);
+    for (StringPiece arg : util::Tokenize(line, ' ')) {
+      arg = util::TrimWhitespace(arg);
+      if (!arg.empty()) {
+        out_argset->emplace(arg);
+      }
     }
   }
   return true;
diff --git a/tools/aapt2/util/Files_test.cpp b/tools/aapt2/util/Files_test.cpp
index 6c38080..618a3e0 100644
--- a/tools/aapt2/util/Files_test.cpp
+++ b/tools/aapt2/util/Files_test.cpp
@@ -25,6 +25,9 @@
 
 using ::android::base::StringPrintf;
 
+using ::testing::ElementsAre;
+using ::testing::UnorderedElementsAre;
+
 namespace aapt {
 namespace file {
 
@@ -34,9 +37,11 @@
 constexpr const char sTestDirSep = '/';
 #endif
 
-class FilesTest : public ::testing::Test {
+class FilesTest : public TestDirectoryFixture {
  public:
   void SetUp() override {
+    TestDirectoryFixture::SetUp();
+
     std::stringstream builder;
     builder << "hello" << sDirSep << "there";
     expected_path_ = builder.str();
@@ -66,6 +71,42 @@
   EXPECT_EQ(expected_path_, base);
 }
 
+TEST_F(FilesTest, AppendArgsFromFile) {
+  const std::string args_file = GetTestPath("args.txt");
+  WriteFile(args_file,
+            "  \n"
+            "arg1 arg2   arg3  \n"
+            "   arg4 arg5");
+  std::vector<std::string> args;
+  std::string error;
+  ASSERT_TRUE(AppendArgsFromFile(args_file, &args, &error));
+  EXPECT_THAT(args, ElementsAre("arg1", "arg2", "arg3", "arg4", "arg5"));
+}
+
+TEST_F(FilesTest, AppendArgsFromFile_InvalidFile) {
+  std::vector<std::string> args;
+  std::string error;
+  ASSERT_FALSE(AppendArgsFromFile(GetTestPath("not_found.txt"), &args, &error));
+}
+
+TEST_F(FilesTest, AppendSetArgsFromFile) {
+  const std::string args_file = GetTestPath("args.txt");
+  WriteFile(args_file,
+            "  \n"
+            "arg2 arg4   arg1  \n"
+            "   arg5 arg3");
+  std::unordered_set<std::string> args;
+  std::string error;
+  ASSERT_TRUE(AppendSetArgsFromFile(args_file, &args, &error));
+  EXPECT_THAT(args, UnorderedElementsAre("arg1", "arg2", "arg3", "arg4", "arg5"));
+}
+
+TEST_F(FilesTest, AppendSetArgsFromFile_InvalidFile) {
+  std::unordered_set<std::string> args;
+  std::string error;
+  ASSERT_FALSE(AppendSetArgsFromFile(GetTestPath("not_found.txt"), &args, &error));
+}
+
 #ifdef _WIN32
 TEST_F(FilesTest, WindowsMkdirsLongPath) {
   // Creating directory paths longer than the Windows maximum path length (260 charatcers) should
diff --git a/tools/codegen/src/com/android/codegen/FileInfo.kt b/tools/codegen/src/com/android/codegen/FileInfo.kt
index a1d0389..cc3a156 100644
--- a/tools/codegen/src/com/android/codegen/FileInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FileInfo.kt
@@ -238,7 +238,7 @@
                     } else if (classBounds.isDataclass) {
 
                         // Insert placeholder for generated code to be inserted for the 1st time
-                        chunks.last = (chunks.last as Code)
+                        chunks[chunks.lastIndex] = (chunks.last() as Code)
                                 .lines
                                 .dropLastWhile { it.isBlank() }
                                 .run {
@@ -286,4 +286,4 @@
                     .let { addAll(it) }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index 9ceb204..a40bdd7 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -137,14 +137,4 @@
             cause)
 }
 
-var <T> MutableList<T>.last
-    get() = last()
-    set(value) {
-        if (isEmpty()) {
-            add(value)
-        } else {
-            this[size - 1] = value
-        }
-    }
-
-inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init)
\ No newline at end of file
+inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init)