Merge "Revert "[CrashRecovery] Add module lib to Android SDK"" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index f0e0c30..45f6570 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -15,6 +15,7 @@
 aconfig_srcjars = [
     ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.biometrics.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}",
@@ -123,6 +124,11 @@
 java_aconfig_library {
     name: "android.nfc.flags-aconfig-java",
     aconfig_declarations: "android.nfc.flags-aconfig",
+    min_sdk_version: "VanillaIceCream",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.nfcservices",
+    ],
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
@@ -147,6 +153,12 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+rust_aconfig_library {
+    name: "libandroid_security_flags_rust",
+    crate_name: "android_security_flags",
+    aconfig_declarations: "android.security.flags-aconfig",
+}
+
 // Package Manager
 aconfig_declarations {
     name: "android.content.pm.flags-aconfig",
@@ -186,3 +198,34 @@
     aconfig_declarations: "com.android.net.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Media
+aconfig_declarations {
+    name: "android.media.playback.flags-aconfig",
+    package: "com.android.media.playback.flags",
+    srcs: ["media/jni/playback_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "android.media.playback.flags-aconfig-cc",
+    aconfig_declarations: "android.media.playback.flags-aconfig",
+}
+
+java_aconfig_library {
+    name: "android.media.playback.flags-aconfig-java",
+    aconfig_declarations: "android.media.playback.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// VCN
+aconfig_declarations {
+    name: "android.net.vcn.flags-aconfig",
+    package: "android.net.vcn",
+    srcs: ["core/java/android/net/vcn/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.net.vcn.flags-aconfig-java",
+    aconfig_declarations: "android.net.vcn.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index ccff26f..1aa297f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -321,7 +321,6 @@
             ":installd_aidl",
             ":libaudioclient_aidl",
             ":libbinder_aidl",
-            ":libbluetooth-binder-aidl",
             ":libcamera_client_aidl",
             ":libcamera_client_framework_aidl",
             ":libupdate_engine_aidl",
@@ -370,17 +369,12 @@
         // TODO(b/120066492): remove default_television.xml when the build system
         // propagates "required" properly.
         "default_television.xml",
-        "framework-platform-compat-config",
         // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build
         // system propagates "required" properly.
         "gps_debug.conf",
-        "icu4j-platform-compat-config",
         "protolog.conf.json.gz",
-        "services-platform-compat-config",
-        "TeleService-platform-compat-config",
-        "documents-ui-compat-config",
-        "calendar-provider-compat-config",
-        "contacts-provider-platform-compat-config",
+        // any install dependencies should go into framework-minus-apex-install-dependencies
+        // rather than here to avoid bloating incremental build time
     ],
     libs: [
         "androidx.annotation_annotation",
@@ -402,6 +396,7 @@
         "soundtrigger_middleware-aidl-java",
         "modules-utils-binary-xml",
         "modules-utils-build",
+        "modules-utils-fastxmlserializer",
         "modules-utils-preconditions",
         "modules-utils-statemachine",
         "modules-utils-synchronous-result-receiver",
@@ -415,21 +410,18 @@
     ],
 }
 
-java_library {
-    name: "framework-minus-apex",
+// Separated so framework-minus-apex-defaults can be used without the libs dependency
+java_defaults {
+    name: "framework-minus-apex-with-libs-defaults",
     defaults: ["framework-minus-apex-defaults"],
-    installable: true,
-    // For backwards compatibility.
-    stem: "framework",
-    apex_available: ["//apex_available:platform"],
-    visibility: [
-        "//frameworks/base",
-        // TODO(b/147128803) remove the below lines
-        "//frameworks/base/apex/blobstore/framework",
-        "//frameworks/base/apex/jobscheduler/framework",
-        "//frameworks/base/packages/Tethering/tests/unit",
-        "//packages/modules/Connectivity/Tethering/tests/unit",
+    libs: [
+        "framework-virtualization.stubs.module_lib",
+        "framework-location.impl",
     ],
+}
+
+java_defaults {
+    name: "framework-non-updatable-lint-defaults",
     lint: {
         extra_check_modules: ["AndroidFrameworkLintChecker"],
         disabled_checks: ["ApiMightLeakAppVisibility"],
@@ -443,6 +435,43 @@
             "UseOfCallerAwareMethodsWithClearedIdentity",
         ],
     },
+}
+
+// we are unfortunately building the turbine jar twice, but more efficient and less complex
+// than generating a similar set of stubs with metalava
+java_library {
+    name: "framework-minus-apex-headers",
+    defaults: ["framework-minus-apex-defaults"],
+    installable: false,
+    // For backwards compatibility.
+    stem: "framework",
+    apex_available: ["//apex_available:platform"],
+    visibility: [
+        "//frameworks/base/location",
+    ],
+    compile_dex: false,
+    headers_only: true,
+}
+
+java_library {
+    name: "framework-minus-apex",
+    defaults: [
+        "framework-minus-apex-with-libs-defaults",
+        "framework-non-updatable-lint-defaults",
+    ],
+    installable: true,
+    // For backwards compatibility.
+    stem: "framework",
+    apex_available: ["//apex_available:platform"],
+    visibility: [
+        "//frameworks/base",
+        "//frameworks/base/location",
+        // TODO(b/147128803) remove the below lines
+        "//frameworks/base/apex/blobstore/framework",
+        "//frameworks/base/apex/jobscheduler/framework",
+        "//frameworks/base/packages/Tethering/tests/unit",
+        "//packages/modules/Connectivity/Tethering/tests/unit",
+    ],
     errorprone: {
         javacflags: [
             "-Xep:AndroidFrameworkCompatChange:ERROR",
@@ -453,7 +482,7 @@
 
 java_library {
     name: "framework-minus-apex-intdefs",
-    defaults: ["framework-minus-apex-defaults"],
+    defaults: ["framework-minus-apex-with-libs-defaults"],
     plugins: ["intdef-annotation-processor"],
 
     // Errorprone and android lint will already run on framework-minus-apex, don't rerun them on
@@ -481,6 +510,7 @@
     installable: false, // this lib is a build-only library
     static_libs: [
         "app-compat-annotations",
+        "framework-location.impl",
         "framework-minus-apex",
         "framework-updatable-stubs-module_libs_api",
     ],
@@ -488,6 +518,20 @@
     apex_available: ["//apex_available:platform"],
 }
 
+java_library {
+    name: "framework-minus-apex-install-dependencies",
+    required: [
+        "framework-minus-apex",
+        "framework-platform-compat-config",
+        "services-platform-compat-config",
+        "icu4j-platform-compat-config",
+        "TeleService-platform-compat-config",
+        "documents-ui-compat-config",
+        "calendar-provider-compat-config",
+        "contacts-provider-platform-compat-config",
+    ],
+}
+
 platform_compat_config {
     name: "framework-platform-compat-config",
     src: ":framework-minus-apex",
@@ -560,46 +604,11 @@
     ],
 }
 
-// TODO(b/145644363): move this to under StubLibraries.bp or ApiDocs.bp
-metalava_framework_docs_args = "" +
-    "--api-lint-ignore-prefix android.icu. " +
-    "--api-lint-ignore-prefix java. " +
-    "--api-lint-ignore-prefix junit. " +
-    "--api-lint-ignore-prefix org. " +
-    "--error NoSettingsProvider " +
-    "--error UnhiddenSystemApi " +
-    "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.* " +
-    "--hide BroadcastBehavior " +
-    "--hide CallbackInterface " +
-    "--hide DeprecationMismatch " +
-    "--hide HiddenSuperclass " +
-    "--hide HiddenTypeParameter " +
-    "--hide MissingPermission " +
-    "--hide-package android.audio.policy.configuration.V7_0 " +
-    "--hide-package com.android.server " +
-    "--hide RequiresPermission " +
-    "--hide SdkConstant " +
-    "--hide Todo " +
-    "--hide UnavailableSymbol " +
-    "--manifest $(location :frameworks-base-core-AndroidManifest.xml) "
-
-packages_to_document = [
-    "android",
-    "dalvik",
-    "java",
-    "javax",
-    "junit",
-    "org.apache.http",
-    "org.json",
-    "org.w3c.dom",
-    "org.xml.sax",
-    "org.xmlpull",
-]
-
 filegroup {
     name: "android-non-updatable-stub-sources",
     srcs: [
         ":framework-mime-sources", // mimemap builds separately but has no separate droidstubs.
+        ":framework-minus-apex-aconfig-srcjars",
         ":framework-non-updatable-sources",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
@@ -609,86 +618,6 @@
     visibility: ["//frameworks/base/api"],
 }
 
-// Defaults for all stubs that include the non-updatable framework. These defaults do not include
-// module symbols, so will not compile correctly on their own. Users must add module APIs to the
-// classpath (or sources) somehow.
-stubs_defaults {
-    name: "android-non-updatable-stubs-defaults",
-    srcs: [":android-non-updatable-stub-sources"],
-    sdk_version: "none",
-    system_modules: "none",
-    java_version: "1.8",
-    arg_files: [":frameworks-base-core-AndroidManifest.xml"],
-    aidl: {
-        include_dirs: [
-            "frameworks/av/aidl",
-            "frameworks/base/media/aidl",
-            "frameworks/base/telephony/java",
-            "frameworks/native/libs/permission/aidl",
-            "packages/modules/Bluetooth/framework/aidl-export",
-            "packages/modules/Connectivity/framework/aidl-export",
-            "packages/modules/Media/apex/aidl/stable",
-            "hardware/interfaces/biometrics/common/aidl",
-            "hardware/interfaces/biometrics/fingerprint/aidl",
-            "hardware/interfaces/graphics/common/aidl",
-            "hardware/interfaces/keymaster/aidl",
-            "system/hardware/interfaces/media/aidl",
-        ],
-    },
-    // These are libs from framework-internal-utils that are required (i.e. being referenced)
-    // from framework-non-updatable-sources. Add more here when there's a need.
-    // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
-    // dependencies gets bigger.
-    libs: [
-        "android.hardware.cas-V1.2-java",
-        "android.hardware.health-V1.0-java-constants",
-        "android.hardware.radio-V1.5-java",
-        "android.hardware.radio-V1.6-java",
-        "android.hardware.thermal-V1.0-java-constants",
-        "android.hardware.thermal-V2.0-java",
-        "android.hardware.tv.input-V1.0-java-constants",
-        "android.hardware.usb-V1.0-java-constants",
-        "android.hardware.usb-V1.1-java-constants",
-        "android.hardware.usb.gadget-V1.0-java",
-        "android.hardware.vibrator-V1.3-java",
-        "framework-protos",
-    ],
-    filter_packages: packages_to_document,
-    high_mem: true, // Lots of sources => high memory use, see b/170701554
-    installable: false,
-    annotations_enabled: true,
-    previous_api: ":android.api.public.latest",
-    merge_annotations_dirs: ["metalava-manual"],
-    defaults_visibility: ["//frameworks/base/api"],
-    visibility: ["//frameworks/base/api"],
-}
-
-// Defaults with module APIs in the classpath (mostly from prebuilts).
-// Suitable for compiling android-non-updatable.
-stubs_defaults {
-    name: "module-classpath-stubs-defaults",
-    aidl: {
-        include_dirs: [
-            "packages/modules/Bluetooth/framework/aidl-export",
-            "packages/modules/Connectivity/framework/aidl-export",
-            "packages/modules/Media/apex/aidl/stable",
-        ],
-    },
-    libs: [
-        "art.module.public.api",
-        "sdk_module-lib_current_framework-tethering",
-        "sdk_module-lib_current_framework-connectivity-t",
-        "sdk_public_current_framework-bluetooth",
-        // There are a few classes from modules used by the core that
-        // need to be resolved by metalava. We use a prebuilt stub of the
-        // full sdk to ensure we can resolve them. If a new class gets added,
-        // the prebuilts/sdk/current needs to be updated.
-        "sdk_system_current_android",
-        // NOTE: The below can be removed once the prebuilt stub contains IKE.
-        "sdk_system_current_android.net.ipsec.ike",
-    ],
-}
-
 build = [
     "AconfigFlags.bp",
     "ProtoLibraries.bp",
diff --git a/BAL_OWNERS b/BAL_OWNERS
new file mode 100644
index 0000000..d56a1d4
--- /dev/null
+++ b/BAL_OWNERS
@@ -0,0 +1,5 @@
+brufino@google.com
+achim@google.com
+topjohnwu@google.com
+lus@google.com
+
diff --git a/INPUT_OWNERS b/INPUT_OWNERS
index e02ba77..44b2f38 100644
--- a/INPUT_OWNERS
+++ b/INPUT_OWNERS
@@ -5,3 +5,5 @@
 prabirmsp@google.com
 svv@google.com
 vdevmurari@google.com
+
+per-file Virtual*=file:/services/companion/java/com/android/server/companion/virtual/OWNERS
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index 45bb161..e7adf20 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -77,6 +77,42 @@
     output_extension: "proto.h",
 }
 
+// ====  nfc framework java library  ==============================
+gensrcs {
+    name: "framework-nfc-javastream-protos",
+
+    tools: [
+        "aprotoc",
+        "protoc-gen-javastream",
+        "soong_zip",
+    ],
+
+    cmd: "mkdir -p $(genDir)/$(in) " +
+        "&& $(location aprotoc) " +
+        "  --plugin=$(location protoc-gen-javastream) " +
+        "  --javastream_out=$(genDir)/$(in) " +
+        "  -Iexternal/protobuf/src " +
+        "  -I . " +
+        "  $(in) " +
+        "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+
+    srcs: [
+        "core/proto/android/app/pendingintent.proto",
+        "core/proto/android/content/component_name.proto",
+        "core/proto/android/content/intent.proto",
+        "core/proto/android/nfc/*.proto",
+        "core/proto/android/os/patternmatcher.proto",
+        "core/proto/android/os/persistablebundle.proto",
+        "core/proto/android/privacy.proto",
+    ],
+
+    data: [
+        ":libprotobuf-internal-protos",
+    ],
+
+    output_extension: "srcjar",
+}
+
 // ====  java proto host library  ==============================
 java_library_host {
     name: "platformprotos",
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index a4a0b4b..83ad736 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -26,6 +26,10 @@
         "unsupportedappusage",
     ],
 
+    static_libs: [
+        "modules-utils-fastxmlserializer",
+    ],
+
     // Rename classes shared with the framework
     jarjar_rules: "jarjar-rules.txt",
 
diff --git a/api/Android.bp b/api/Android.bp
index c16bce5..bd2b11a 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -86,6 +86,7 @@
         "framework-devicelock",
         "framework-graphics",
         "framework-healthfitness",
+        "framework-location",
         "framework-media",
         "framework-mediaprovider",
         "framework-ondevicepersonalization",
@@ -259,6 +260,155 @@
     cmd: "cat $(in) | md5sum | cut -d' ' -f1 > $(out)",
 }
 
+packages_to_document = [
+    "android",
+    "dalvik",
+    "java",
+    "javax",
+    "junit",
+    "org.apache.http",
+    "org.json",
+    "org.w3c.dom",
+    "org.xml.sax",
+    "org.xmlpull",
+]
+
+// Defaults for all stubs that include the non-updatable framework. These defaults do not include
+// module symbols, so will not compile correctly on their own. Users must add module APIs to the
+// classpath (or sources) somehow.
+stubs_defaults {
+    name: "android-non-updatable-stubs-defaults",
+    srcs: [":android-non-updatable-stub-sources"],
+    sdk_version: "none",
+    system_modules: "none",
+    java_version: "1.8",
+    arg_files: [":frameworks-base-core-AndroidManifest.xml"],
+    aidl: {
+        include_dirs: [
+            "frameworks/av/aidl",
+            "frameworks/base/media/aidl",
+            "frameworks/base/telephony/java",
+            "frameworks/native/libs/permission/aidl",
+            "packages/modules/Bluetooth/framework/aidl-export",
+            "packages/modules/Connectivity/framework/aidl-export",
+            "packages/modules/Media/apex/aidl/stable",
+            "hardware/interfaces/biometrics/common/aidl",
+            "hardware/interfaces/biometrics/fingerprint/aidl",
+            "hardware/interfaces/graphics/common/aidl",
+            "hardware/interfaces/keymaster/aidl",
+            "system/hardware/interfaces/media/aidl",
+        ],
+    },
+    // These are libs from framework-internal-utils that are required (i.e. being referenced)
+    // from framework-non-updatable-sources. Add more here when there's a need.
+    // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
+    // dependencies gets bigger.
+    libs: [
+        "android.hardware.cas-V1.2-java",
+        "android.hardware.health-V1.0-java-constants",
+        "android.hardware.radio-V1.5-java",
+        "android.hardware.radio-V1.6-java",
+        "android.hardware.thermal-V1.0-java-constants",
+        "android.hardware.thermal-V2.0-java",
+        "android.hardware.tv.input-V1.0-java-constants",
+        "android.hardware.usb-V1.0-java-constants",
+        "android.hardware.usb-V1.1-java-constants",
+        "android.hardware.usb.gadget-V1.0-java",
+        "android.hardware.vibrator-V1.3-java",
+        "framework-protos",
+    ],
+    flags: [
+        "--api-lint-ignore-prefix android.icu.",
+        "--api-lint-ignore-prefix java.",
+        "--api-lint-ignore-prefix junit.",
+        "--api-lint-ignore-prefix org.",
+        "--error NoSettingsProvider",
+        "--error UnhiddenSystemApi",
+        "--error UnflaggedApi",
+        "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*",
+        "--hide BroadcastBehavior",
+        "--hide CallbackInterface",
+        "--hide DeprecationMismatch",
+        "--hide HiddenSuperclass",
+        "--hide MissingPermission",
+        "--hide RequiresPermission",
+        "--hide SdkConstant",
+        "--hide Todo",
+        "--hide-package android.audio.policy.configuration.V7_0",
+        "--hide-package com.android.server",
+        "--manifest $(location :frameworks-base-core-AndroidManifest.xml)",
+    ],
+    filter_packages: packages_to_document,
+    high_mem: true, // Lots of sources => high memory use, see b/170701554
+    installable: false,
+    annotations_enabled: true,
+    previous_api: ":android.api.public.latest",
+    merge_annotations_dirs: ["metalava-manual"],
+    defaults_visibility: ["//frameworks/base/api"],
+    visibility: ["//frameworks/base/api"],
+}
+
+// We resolve dependencies on APIs in modules by depending on a prebuilt of the whole
+// platform (sdk_system_current_android). That prebuilt does not include module-lib APIs,
+// so use the prebuilt module-lib stubs for modules that export module-lib stubs that the
+// non-updatable part depends on.
+non_updatable_api_deps_on_modules = [
+    "sdk_module-lib_current_framework-tethering",
+    "sdk_module-lib_current_framework-connectivity-t",
+    "sdk_system_current_android",
+]
+
+// Defaults with module APIs in the classpath (mostly from prebuilts).
+// Suitable for compiling android-non-updatable.
+stubs_defaults {
+    name: "module-classpath-stubs-defaults",
+    aidl: {
+        include_dirs: [
+            "packages/modules/Bluetooth/framework/aidl-export",
+            "packages/modules/Connectivity/framework/aidl-export",
+            "packages/modules/Media/apex/aidl/stable",
+        ],
+    },
+    libs: non_updatable_api_deps_on_modules,
+}
+
+// Defaults for the java_sdk_libraries of unbundled jars from framework.
+// java_sdk_libraries using these defaults should also add themselves to the
+// non_updatable_modules list in frameworks/base/api/api.go
+java_defaults {
+    name: "framework-non-updatable-unbundled-defaults",
+    defaults: [
+        "framework-non-updatable-lint-defaults",
+        "non-updatable-framework-module-defaults",
+    ],
+    public: {
+        libs: ["android_module_lib_stubs_current"],
+    },
+    system: {
+        libs: ["android_module_lib_stubs_current"],
+    },
+    module_lib: {
+        libs: ["android_module_lib_stubs_current"],
+    },
+    test: {
+        libs: ["android_test_frameworks_core_stubs_current"],
+    },
+    sdk_version: "core_platform",
+    stub_only_libs: ["framework-protos"],
+    impl_only_libs: ["framework-minus-apex-headers"], // the framework, including hidden API
+    impl_library_visibility: ["//frameworks/base"],
+    defaults_visibility: ["//frameworks/base/location"],
+    plugins: ["error_prone_android_framework"],
+    errorprone: {
+        javacflags: [
+            "-Xep:AndroidFrameworkCompatChange:ERROR",
+            "-Xep:AndroidFrameworkUid:ERROR",
+        ],
+    },
+    // Include manual annotations in API txt files
+    merge_annotations_dirs: ["metalava-manual"],
+}
+
 build = [
     "ApiDocs.bp",
     "StubLibraries.bp",
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 30b4423..5744bdf 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -72,7 +72,6 @@
         "android-non-updatable-doc-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args,
 }
 
 droidstubs {
@@ -81,8 +80,7 @@
         "android-non-updatable-doc-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
+    flags: ["--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)"],
 }
 
 droidstubs {
@@ -91,9 +89,10 @@
         "android-non-updatable-doc-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) " +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\) ",
+    flags: [
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)",
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)",
+    ],
     generate_stubs: false, // We're only using this module for the annotations.zip output, disable doc-stubs.
     write_sdk_values: false,
 }
@@ -104,10 +103,11 @@
         "android-non-updatable-doc-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) " +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\) " +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\) ",
+    flags: [
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)",
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)",
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)",
+    ],
     generate_stubs: false, // We're only using this module for the annotations.zip output, disable doc-stubs.
     write_sdk_values: false,
 }
@@ -116,7 +116,6 @@
     name: "framework-doc-stubs",
     defaults: ["android-non-updatable-doc-stubs-defaults"],
     srcs: [":all-modules-public-stubs-source"],
-    args: metalava_framework_docs_args,
     api_levels_module: "api_versions_public",
     aidl: {
         include_dirs: [
@@ -124,21 +123,13 @@
             "packages/modules/Media/apex/aidl/stable",
         ],
     },
-    extensions_info_file: ":sdk-extensions-info",
 }
 
 droidstubs {
     name: "framework-doc-system-stubs",
     defaults: ["framework-doc-stubs-sources-default"],
-    args: metalava_framework_docs_args +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
-    api_levels_annotations_enabled: true,
-    api_levels_annotations_dirs: [
-        "sdk-dir",
-        "api-versions-jars-dir",
-    ],
-    api_levels_sdk_type: "system",
-    extensions_info_file: ":sdk-extensions-info",
+    flags: ["--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)"],
+    api_levels_module: "api_versions_system",
 }
 
 /////////////////////////////////////////////////////////////////////
@@ -146,17 +137,6 @@
 // using droiddoc
 /////////////////////////////////////////////////////////////////////
 
-framework_docs_only_args = " -android -manifest $(location :frameworks-base-core-AndroidManifest.xml) " +
-    "-metalavaApiSince " +
-    "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
-    "-overview $(location :frameworks-base-java-overview) " +
-    // Federate Support Library references against local API file.
-    "-federate SupportLib https://developer.android.com " +
-    "-federationapi SupportLib $(location :current-support-api) " +
-    // Federate Support Library references against local API file.
-    "-federate AndroidX https://developer.android.com " +
-    "-federationapi AndroidX $(location :current-androidx-api) "
-
 doc_defaults {
     name: "framework-docs-default",
     sdk_version: "none",
@@ -176,6 +156,28 @@
     resourcesdir: "docs/html/reference/images/",
     resourcesoutdir: "reference/android/images/",
     lint_baseline: "javadoc-lint-baseline",
+    flags: [
+        "-android",
+        "-manifest $(location :frameworks-base-core-AndroidManifest.xml)",
+        "-metalavaApiSince",
+        "-werror",
+        "-lerror",
+        "-overview $(location :frameworks-base-java-overview)",
+        // Federate Support Library references against local API file.
+        "-federate SupportLib https://developer.android.com",
+        "-federationapi SupportLib $(location :current-support-api)",
+        // Federate Support Library references against local API file.
+        "-federate AndroidX https://developer.android.com",
+        "-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 111", // HIDDEN_SUPERCLASS
+        "-hide 113", // DEPRECATION_MISMATCH
+        "-hide 125", // REQUIRES_PERMISSION
+        "-hide 126", // BROADCAST_BEHAVIOR
+        "-hide 127", // SDK_CONSTANT
+        "-hide 128", // TODO
+    ],
     hdf: [
         "dac true",
         "sdk.codename O",
@@ -211,7 +213,10 @@
     ],
     compat_config: ":global-compat-config",
     proofread_file: "offline-sdk-docs-proofread.txt",
-    args: framework_docs_only_args + " -offlinemode -title \"Android SDK\"",
+    flags: [
+        "-offlinemode",
+        "-title \"Android SDK\"",
+    ],
     static_doc_index_redirect: "docs/docs-preview-index.html",
 }
 
@@ -228,7 +233,11 @@
         "android.whichdoc offline",
     ],
     proofread_file: "offline-sdk-referenceonly-docs-proofread.txt",
-    args: framework_docs_only_args + " -offlinemode -title \"Android SDK\" -referenceonly",
+    flags: [
+        "-offlinemode",
+        "-title \"Android SDK\"",
+        "-referenceonly",
+    ],
     static_doc_index_redirect: "docs/docs-documentation-redirect.html",
     static_doc_properties: "docs/source.properties",
 }
@@ -246,8 +255,14 @@
         "android.whichdoc offline",
     ],
     proofread_file: "offline-system-sdk-referenceonly-docs-proofread.txt",
-    args: framework_docs_only_args + " -hide 101 -hide 104 -hide 108" +
-    " -offlinemode -title \"Android System SDK\" -referenceonly",
+    flags: [
+        "-hide 101",
+        "-hide 104",
+        "-hide 108",
+        "-offlinemode",
+        "-title \"Android System SDK\"",
+        "-referenceonly",
+    ],
     static_doc_index_redirect: "docs/docs-documentation-redirect.html",
     static_doc_properties: "docs/source.properties",
 }
@@ -263,22 +278,28 @@
         "android.hasSamples true",
     ],
     proofread_file: "ds-docs-proofread.txt",
-    args: framework_docs_only_args +
-        " -toroot / -yamlV2 -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -devsite -samplesdir development/samples/browseable ",
+    flags: [
+        " -toroot /",
+        "-yamlV2",
+        "-samplegroup Admin",
+        "-samplegroup Background",
+        "-samplegroup Connectivity",
+        "-samplegroup Content",
+        "-samplegroup Input",
+        "-samplegroup Media",
+        "-samplegroup Notification",
+        "-samplegroup RenderScript",
+        "-samplegroup Security",
+        "-samplegroup Sensors",
+        "-samplegroup System",
+        "-samplegroup Testing",
+        "-samplegroup UI",
+        "-samplegroup Views",
+        "-samplegroup Wearable",
+        "-devsite",
+        "-samplesdir",
+        "development/samples/browseable",
+    ],
 }
 
 droiddoc {
@@ -286,8 +307,11 @@
     srcs: [
         ":framework-doc-stubs",
     ],
-    args: "-noJdkLink -links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list " +
+    flags: [
+        "-noJdkLink",
+        "-links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list",
         "-noStdlibLink",
+    ],
     proofread_file: "ds-dokka-proofread.txt",
     dokka_enabled: true,
 }
@@ -340,11 +364,12 @@
     hdf: [
         "android.whichdoc online",
     ],
-    args: framework_docs_only_args +
-        " -staticonly " +
-        " -toroot / " +
-        " -devsite " +
-        " -ignoreJdLinks ",
+    flags: [
+        "-staticonly",
+        "-toroot /",
+        "-devsite",
+        "-ignoreJdLinks",
+    ],
 }
 
 droiddoc {
@@ -356,8 +381,9 @@
     hdf: [
         "android.whichdoc online",
     ],
-    args: framework_docs_only_args +
-        " -toroot / " +
-        " -atLinksNavtree " +
-        " -navtreeonly ",
+    flags: [
+        "-toroot /",
+        "-atLinksNavtree",
+        "-navtreeonly",
+    ],
 }
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 79eface..f6f6929 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -29,14 +29,10 @@
 
 droidstubs {
     name: "api-stubs-docs-non-updatable",
-    srcs: [
-        ":framework-minus-apex-aconfig-srcjars",
-    ],
     defaults: [
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args,
     check_api: {
         current: {
             api_file: ":non-updatable-current.txt",
@@ -50,6 +46,7 @@
         api_lint: {
             enabled: true,
             new_since: ":android.api.public.latest",
+            baseline_file: ":non-updatable-lint-baseline.txt",
         },
     },
     dists: [
@@ -69,30 +66,33 @@
     api_surface: "public",
 }
 
-priv_apps = " --show-annotation android.annotation.SystemApi\\(" +
-    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
-    "\\)"
+priv_apps = [
+    "--show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
+        "\\)",
+]
 
-priv_apps_in_stubs = " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" +
-    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
-    "\\)"
+priv_apps_in_stubs = [
+    "--show-for-stub-purposes-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
+        "\\)",
+]
 
-test = " --show-annotation android.annotation.TestApi"
+test = ["--show-annotation android.annotation.TestApi"]
 
-module_libs = " --show-annotation android.annotation.SystemApi\\(" +
-    "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
-    "\\)"
+module_libs = [
+    "--show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
+        "\\)",
+]
 
 droidstubs {
     name: "system-api-stubs-docs-non-updatable",
-    srcs: [
-        ":framework-minus-apex-aconfig-srcjars",
-    ],
     defaults: [
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args + priv_apps,
+    flags: priv_apps,
     check_api: {
         current: {
             api_file: ":non-updatable-system-current.txt",
@@ -128,14 +128,11 @@
 
 droidstubs {
     name: "test-api-stubs-docs-non-updatable",
-    srcs: [
-        ":framework-minus-apex-aconfig-srcjars",
-    ],
     defaults: [
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args + test + priv_apps_in_stubs,
+    flags: test + priv_apps_in_stubs,
     check_api: {
         current: {
             api_file: ":non-updatable-test-current.txt",
@@ -143,6 +140,7 @@
         },
         api_lint: {
             enabled: true,
+            new_since: ":android.api.test.latest",
             baseline_file: ":non-updatable-test-lint-baseline.txt",
         },
     },
@@ -177,14 +175,11 @@
 
 droidstubs {
     name: "module-lib-api-stubs-docs-non-updatable",
-    srcs: [
-        ":framework-minus-apex-aconfig-srcjars",
-    ],
     defaults: [
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs,
+    flags: priv_apps_in_stubs + module_libs,
     check_api: {
         current: {
             api_file: ":non-updatable-module-lib-current.txt",
@@ -356,17 +351,7 @@
         "android-non-updatable_from_source_defaults",
     ],
     srcs: [":module-lib-api-stubs-docs-non-updatable"],
-    libs: [
-        // We cannot depend on all-modules-module-lib-stubs, because the module-lib stubs
-        // depend on this stub. We resolve dependencies on APIs in modules by depending
-        // on a prebuilt of the whole platform (sdk_system_current_android).
-        // That prebuilt does not include module-lib APIs, so use the prebuilt module-lib
-        // stubs for modules that export module-lib stubs that the non-updatable part
-        // depends on.
-        "sdk_module-lib_current_framework-tethering",
-        "sdk_module-lib_current_framework-connectivity-t",
-        "sdk_system_current_android",
-    ],
+    libs: non_updatable_api_deps_on_modules,
     dist: {
         dir: "apistubs/android/module-lib",
     },
@@ -521,6 +506,21 @@
 }
 
 java_library {
+    name: "android_test_frameworks_core_stubs_current.from-source",
+    static_libs: [
+        "all-updatable-modules-system-stubs",
+        "android-non-updatable.stubs.test",
+    ],
+    defaults: [
+        "android.jar_defaults",
+        "android_stubs_dists_default",
+    ],
+    dist: {
+        dir: "apistubs/android/test-core",
+    },
+}
+
+java_library {
     name: "android_module_lib_stubs_current.from-source",
     defaults: [
         "android.jar_defaults",
@@ -634,6 +634,7 @@
     api_surface: "test",
     api_contributions: [
         "framework-virtualization.stubs.source.test.api.contribution",
+        "framework-location.stubs.source.test.api.contribution",
     ],
 }
 
@@ -677,6 +678,7 @@
         "api-stubs-docs-non-updatable.api.contribution",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -692,6 +694,7 @@
         "system-api-stubs-docs-non-updatable.api.contribution",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -709,6 +712,7 @@
         "test-api-stubs-docs-non-updatable.api.contribution",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -718,12 +722,15 @@
         "android_stubs_current_contributions",
         "android_system_stubs_current_contributions",
         "android_test_frameworks_core_stubs_current_contributions",
-        "stub-annotation-defaults",
+    ],
+    libs: [
+        "stub-annotations",
     ],
     api_contributions: [
         "api-stubs-docs-non-updatable.api.contribution",
         "system-api-stubs-docs-non-updatable.api.contribution",
     ],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -743,6 +750,7 @@
         "module-lib-api-stubs-docs-non-updatable.api.contribution",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -756,6 +764,7 @@
         "stub-annotations",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -780,6 +789,7 @@
     visibility: [
         "//visibility:private",
     ],
+    enable_validation: false,
 }
 
 java_api_library {
@@ -796,6 +806,7 @@
         "android_module_lib_stubs_current.from-text",
     ],
     visibility: ["//visibility:public"],
+    enable_validation: false,
 }
 
 ////////////////////////////////////////////////////////////////////////
@@ -944,7 +955,7 @@
     merge_annotations_dirs: [
         "metalava-manual",
     ],
-    args: priv_apps,
+    flags: priv_apps,
 }
 
 java_library {
diff --git a/api/api.go b/api/api.go
index 692d38f..8df6dab 100644
--- a/api/api.go
+++ b/api/api.go
@@ -31,6 +31,7 @@
 const conscrypt = "conscrypt.module.public.api"
 const i18n = "i18n.module.public.api"
 const virtualization = "framework-virtualization"
+const location = "framework-location"
 
 var core_libraries_modules = []string{art, conscrypt, i18n}
 
@@ -42,7 +43,7 @@
 // APIs.
 // In addition, the modules in this list are allowed to contribute to test APIs
 // stubs.
-var non_updatable_modules = []string{virtualization}
+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
@@ -296,8 +297,10 @@
 }
 
 func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
-	// The user of this module compiles against the "core" SDK, so remove core libraries to avoid dupes.
+	// 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")
 	props.Static_libs = transformArray(modules, "", ".stubs.module_lib")
@@ -393,6 +396,7 @@
 		"android_stubs_current",
 		"android_system_stubs_current",
 		"android_test_stubs_current",
+		"android_test_frameworks_core_stubs_current",
 		"android_module_lib_stubs_current",
 		"android_system_server_stubs_current",
 	}
diff --git a/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh
index 71f366a..e0153f7 100755
--- a/api/gen_combined_removed_dex.sh
+++ b/api/gen_combined_removed_dex.sh
@@ -6,6 +6,6 @@
 
 # Convert each removed.txt to the "dex format" equivalent, and print all output.
 for f in "$@"; do
-    "$metalava_path" "$f" --dex-api "${tmp_dir}/tmp"
+    "$metalava_path" signature-to-dex "$f" "${tmp_dir}/tmp"
     cat "${tmp_dir}/tmp"
 done
diff --git a/cmds/svc/src/com/android/commands/svc/NfcCommand.java b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
index 020ca33..ee2af12 100644
--- a/cmds/svc/src/com/android/commands/svc/NfcCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
@@ -16,10 +16,11 @@
 
 package com.android.commands.svc;
 
+import android.app.ActivityThread;
 import android.content.Context;
-import android.nfc.INfcAdapter;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
+import android.os.Looper;
 
 public class NfcCommand extends Svc.Command {
 
@@ -42,27 +43,26 @@
 
     @Override
     public void run(String[] args) {
-        INfcAdapter adapter = INfcAdapter.Stub.asInterface(
-                ServiceManager.getService(Context.NFC_SERVICE));
-
+        Looper.prepareMainLooper();
+        ActivityThread.initializeMainlineModules();
+        Context context = ActivityThread.systemMain().getSystemContext();
+        NfcManager nfcManager = context.getSystemService(NfcManager.class);
+        if (nfcManager == null) {
+            System.err.println("Got a null NfcManager, is the system running?");
+            return;
+        }
+        NfcAdapter adapter = nfcManager.getDefaultAdapter();
         if (adapter == null) {
             System.err.println("Got a null NfcAdapter, is the system running?");
             return;
         }
-
-        try {
-            if (args.length == 2 && "enable".equals(args[1])) {
-                adapter.enable();
-                return;
-            } else if (args.length == 2 && "disable".equals(args[1])) {
-                adapter.disable(true);
-                return;
-            }
-        } catch (RemoteException e) {
-            System.err.println("NFC operation failed: " + e);
+        if (args.length == 2 && "enable".equals(args[1])) {
+            adapter.enable();
+            return;
+        } else if (args.length == 2 && "disable".equals(args[1])) {
+            adapter.disable(true);
             return;
         }
-
         System.err.println(longHelp());
     }
 
diff --git a/cmds/svc/src/com/android/commands/svc/OWNERS b/cmds/svc/src/com/android/commands/svc/OWNERS
new file mode 100644
index 0000000..d5a5d7b
--- /dev/null
+++ b/cmds/svc/src/com/android/commands/svc/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+per-file NfcCommand.java = file:platform/packages/apps/Nfc:/OWNERS
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 71a2ca2..907916a 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -38,6 +38,11 @@
 }
 
 filegroup {
+    name: "non-updatable-lint-baseline.txt",
+    srcs: ["lint-baseline.txt"],
+}
+
+filegroup {
     name: "non-updatable-system-current.txt",
     srcs: ["system-current.txt"],
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index e8988dc..c9cba00 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android {
 
   public final class Manifest {
@@ -4458,6 +4456,7 @@
     method public boolean onPreparePanel(int, @Nullable android.view.View, @NonNull android.view.Menu);
     method public void onProvideAssistContent(android.app.assist.AssistContent);
     method public void onProvideAssistData(android.os.Bundle);
+    method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int);
     method public android.net.Uri onProvideReferrer();
     method public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[]);
     method @CallSuper protected void onRestart();
@@ -5677,6 +5676,7 @@
     ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet);
     ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet, int);
     method @Deprecated public void onBackStackChanged();
+    method @Deprecated protected void onLayout(boolean, int, int, int, int);
     method @Deprecated public void setActivity(android.app.Activity);
     method @Deprecated public void setMaxVisible(int);
     method @Deprecated public void setOnBreadCrumbClickListener(android.app.FragmentBreadCrumbs.OnBreadCrumbClickListener);
@@ -12738,7 +12738,7 @@
     field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio.access";
     field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription";
     field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
-    field public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
+    field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
     field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
@@ -13542,6 +13542,7 @@
 
   public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser android.util.AttributeSet java.lang.AutoCloseable {
     method public void close();
+    method public String getAttributeNamespace(int);
   }
 
 }
@@ -13770,6 +13771,7 @@
     method public String getColumnName(int);
     method public android.os.Bundle getExtras();
     method public android.net.Uri getNotificationUri();
+    method public java.util.List<android.net.Uri> getNotificationUris();
     method public final int getPosition();
     method public int getType(int);
     method @Deprecated protected Object getUpdatedField(int);
@@ -13795,6 +13797,7 @@
     method public android.os.Bundle respond(android.os.Bundle);
     method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
+    method public void setNotificationUris(@NonNull android.content.ContentResolver, @NonNull java.util.List<android.net.Uri>);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
     field @Deprecated protected boolean mClosed;
@@ -13926,6 +13929,7 @@
     method public boolean hasNext();
     method public java.util.Iterator<android.database.CursorJoiner.Result> iterator();
     method public android.database.CursorJoiner.Result next();
+    method public void remove();
   }
 
   public enum CursorJoiner.Result {
@@ -13993,6 +13997,7 @@
     method public int getInt(int);
     method public long getLong(int);
     method public android.net.Uri getNotificationUri();
+    method public java.util.List<android.net.Uri> getNotificationUris();
     method public int getPosition();
     method public short getShort(int);
     method public String getString(int);
@@ -14017,6 +14022,7 @@
     method public android.os.Bundle respond(android.os.Bundle);
     method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
+    method public void setNotificationUris(android.content.ContentResolver, java.util.List<android.net.Uri>);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
   }
@@ -18291,11 +18297,13 @@
   public class BiometricManager {
     method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate();
     method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int);
+    method @FlaggedApi("android.hardware.biometrics.last_authentication_time") @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public long getLastAuthenticationTime(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public android.hardware.biometrics.BiometricManager.Strings getStrings(int);
     field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
     field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
     field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
     field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf
+    field @FlaggedApi("android.hardware.biometrics.last_authentication_time") public static final long BIOMETRIC_NO_AUTHENTICATION = -1L; // 0xffffffffffffffffL
     field public static final int BIOMETRIC_SUCCESS = 0; // 0x0
   }
 
@@ -18341,6 +18349,7 @@
     field public static final int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
     field public static final int BIOMETRIC_ERROR_USER_CANCELED = 10; // 0xa
     field public static final int BIOMETRIC_ERROR_VENDOR = 8; // 0x8
+    field @FlaggedApi("android.hardware.biometrics.last_authentication_time") public static final long BIOMETRIC_NO_AUTHENTICATION = -1L; // 0xffffffffffffffffL
   }
 
   public abstract static class BiometricPrompt.AuthenticationCallback {
@@ -18375,12 +18384,12 @@
     ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
     ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession);
     ctor @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") public BiometricPrompt.CryptoObject(@NonNull javax.crypto.KeyAgreement);
-    method public javax.crypto.Cipher getCipher();
+    method @Nullable public javax.crypto.Cipher getCipher();
     method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
     method @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") @Nullable public javax.crypto.KeyAgreement getKeyAgreement();
-    method public javax.crypto.Mac getMac();
+    method @Nullable public javax.crypto.Mac getMac();
     method @Nullable public android.security.identity.PresentationSession getPresentationSession();
-    method public java.security.Signature getSignature();
+    method @Nullable public java.security.Signature getSignature();
   }
 
 }
@@ -20227,7 +20236,7 @@
     method @Deprecated public boolean isPreviewEnabled();
     method @Deprecated public boolean isProximityCorrectionEnabled();
     method @Deprecated public boolean isShifted();
-    method public void onClick(android.view.View);
+    method @Deprecated public void onClick(android.view.View);
     method @Deprecated public void onDetachedFromWindow();
     method @Deprecated public void onDraw(android.graphics.Canvas);
     method @Deprecated protected boolean onLongPress(android.inputmethodservice.Keyboard.Key);
@@ -22641,6 +22650,9 @@
     ctor public MediaCodec.CryptoException(int, @Nullable String);
     method @Nullable public android.media.MediaCodec.CryptoInfo getCryptoInfo();
     method public int getErrorCode();
+    method public int getErrorContext();
+    method public int getOemError();
+    method public int getVendorError();
     field @Deprecated public static final int ERROR_FRAME_TOO_LARGE = 8; // 0x8
     field @Deprecated public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4; // 0x4
     field @Deprecated public static final int ERROR_INSUFFICIENT_SECURITY = 7; // 0x7
@@ -23153,6 +23165,9 @@
 
   public final class MediaCryptoException extends java.lang.Exception implements android.media.MediaDrmThrowable {
     ctor public MediaCryptoException(@Nullable String);
+    method public int getErrorContext();
+    method public int getOemError();
+    method public int getVendorError();
   }
 
   public abstract class MediaDataSource implements java.io.Closeable {
@@ -23377,6 +23392,9 @@
   public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException implements android.media.MediaDrmThrowable {
     method @NonNull public String getDiagnosticInfo();
     method public int getErrorCode();
+    method public int getErrorContext();
+    method public int getOemError();
+    method public int getVendorError();
     method public boolean isTransient();
   }
 
@@ -23450,6 +23468,9 @@
   public static final class MediaDrm.SessionException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable {
     ctor public MediaDrm.SessionException(int, @Nullable String);
     method @Deprecated public int getErrorCode();
+    method public int getErrorContext();
+    method public int getOemError();
+    method public int getVendorError();
     method public boolean isTransient();
     field @Deprecated public static final int ERROR_RESOURCE_CONTENTION = 1; // 0x1
     field @Deprecated public static final int ERROR_UNKNOWN = 0; // 0x0
@@ -23457,6 +23478,9 @@
 
   public class MediaDrmException extends java.lang.Exception implements android.media.MediaDrmThrowable {
     ctor public MediaDrmException(String);
+    method public int getErrorContext();
+    method public int getOemError();
+    method public int getVendorError();
   }
 
   public class MediaDrmResetException extends java.lang.IllegalStateException implements android.media.MediaDrmThrowable {
@@ -24759,7 +24783,7 @@
 
   @Deprecated public class RemoteControlClient.MetadataEditor extends android.media.MediaMetadataEditor {
     method @Deprecated public void apply();
-    method public Object clone() throws java.lang.CloneNotSupportedException;
+    method @Deprecated public Object clone() throws java.lang.CloneNotSupportedException;
     method @Deprecated public android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException;
     method @Deprecated public android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException;
     method @Deprecated public android.media.RemoteControlClient.MetadataEditor putObject(int, Object) throws java.lang.IllegalArgumentException;
@@ -27613,6 +27637,7 @@
     method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
     method public String getSelectedTrack(int);
     method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
+    method protected void onLayout(boolean, int, int, int, int);
     method public boolean onUnhandledInputEvent(android.view.InputEvent);
     method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource);
     method public void reset();
@@ -28950,6 +28975,7 @@
     method @NonNull public long[] getRetryIntervalsMillis();
     method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
     method public boolean hasGatewayOption(int);
+    method @FlaggedApi("android.net.vcn.safe_mode_config") public boolean isSafeModeEnabled();
     field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
   }
 
@@ -28963,6 +28989,7 @@
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
+    method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setSafeModeEnabled(boolean);
     method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
   }
 
@@ -32321,6 +32348,7 @@
     method public void surfaceCreated(android.view.SurfaceHolder);
     method public void surfaceDestroyed(android.view.SurfaceHolder);
     method @Deprecated public void surfaceRedrawNeeded(android.view.SurfaceHolder);
+    method public void surfaceRedrawNeededAsync(android.view.SurfaceHolder, Runnable);
     field public static final int DEBUG_CHECK_GL_ERROR = 1; // 0x1
     field public static final int DEBUG_LOG_GL_CALLS = 2; // 0x2
     field public static final int RENDERMODE_CONTINUOUSLY = 1; // 0x1
@@ -32661,7 +32689,7 @@
     field public static final int S_V2 = 32; // 0x20
     field public static final int TIRAMISU = 33; // 0x21
     field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22
-    field public static final int VANILLA_ICE_CREAM = 10000; // 0x2710
+    field @FlaggedApi("android.os.android_os_build_vanilla_ice_cream") public static final int VANILLA_ICE_CREAM = 10000; // 0x2710
   }
 
   public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
@@ -33962,7 +33990,7 @@
     field public static final String DISALLOW_MICROPHONE_TOGGLE = "disallow_microphone_toggle";
     field public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
     field public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media";
-    field public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio";
+    field @FlaggedApi("android.nfc.enable_nfc_user_restriction") public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio";
     field public static final String DISALLOW_NETWORK_RESET = "no_network_reset";
     field public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
     field public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
@@ -44040,7 +44068,6 @@
     method public int getLongitude();
     method public int getNetworkId();
     method public int getSystemId();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
   }
 
@@ -44056,7 +44083,6 @@
     method @Nullable public String getMncString();
     method @Nullable public String getMobileNetworkOperator();
     method @Deprecated public int getPsc();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
   }
 
@@ -44074,7 +44100,6 @@
     method @Nullable public String getMobileNetworkOperator();
     method public int getPci();
     method public int getTac();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityLte> CREATOR;
   }
 
@@ -44087,7 +44112,6 @@
     method @IntRange(from=0, to=3279165) public int getNrarfcn();
     method @IntRange(from=0, to=1007) public int getPci();
     method @IntRange(from=0, to=16777215) public int getTac();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;
   }
 
@@ -44101,7 +44125,6 @@
     method @Nullable public String getMncString();
     method @Nullable public String getMobileNetworkOperator();
     method public int getUarfcn();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityTdscdma> CREATOR;
   }
 
@@ -44117,7 +44140,6 @@
     method @Nullable public String getMobileNetworkOperator();
     method public int getPsc();
     method public int getUarfcn();
-    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR;
   }
 
@@ -44201,6 +44223,7 @@
 
   public final class CellSignalStrengthCdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getCdmaDbm();
     method public int getCdmaEcio();
@@ -44211,24 +44234,28 @@
     method public int getEvdoLevel();
     method public int getEvdoSnr();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthCdma> CREATOR;
   }
 
   public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getBitErrorRate();
     method public int getDbm();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
     method public int getRssi();
     method public int getTimingAdvance();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthGsm> CREATOR;
   }
 
   public final class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method @IntRange(from=0, to=15) public int getCqi();
     method @IntRange(from=1, to=6) public int getCqiTableIndex();
@@ -44239,12 +44266,14 @@
     method public int getRssi();
     method public int getRssnr();
     method public int getTimingAdvance();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthLte> CREATOR;
   }
 
   public final class CellSignalStrengthNr extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method @IntRange(from=0, to=15) @NonNull public java.util.List<java.lang.Integer> getCsiCqiReport();
     method @IntRange(from=1, to=3) public int getCsiCqiTableIndex();
@@ -44257,26 +44286,31 @@
     method public int getSsRsrq();
     method public int getSsSinr();
     method @IntRange(from=0, to=1282) public int getTimingAdvanceMicros();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthNr> CREATOR;
   }
 
   public final class CellSignalStrengthTdscdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getDbm();
     method @IntRange(from=0, to=4) public int getLevel();
     method public int getRscp();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthTdscdma> CREATOR;
   }
 
   public final class CellSignalStrengthWcdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean equals(Object);
     method public int getAsuLevel();
     method public int getDbm();
     method public int getEcNo();
     method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
+    method public int hashCode();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
   }
@@ -47056,6 +47090,7 @@
     method @Deprecated public int length();
     method @Deprecated public static android.text.AlteredCharSequence make(CharSequence, char[], int, int);
     method @Deprecated public CharSequence subSequence(int, int);
+    method @Deprecated public String toString();
   }
 
   @Deprecated public class AndroidCharacter {
@@ -47469,6 +47504,7 @@
     method public void removeSpan(Object);
     method public void setSpan(Object, int, int, int);
     method public CharSequence subSequence(int, int);
+    method public String toString();
   }
 
   public static final class PrecomputedText.Params {
@@ -47598,6 +47634,7 @@
     method public void setFilters(android.text.InputFilter[]);
     method public void setSpan(Object, int, int, int);
     method public CharSequence subSequence(int, int);
+    method public String toString();
     method public static android.text.SpannableStringBuilder valueOf(CharSequence);
   }
 
@@ -47771,6 +47808,7 @@
     method public boolean hasNext();
     method public java.util.Iterator<java.lang.String> iterator();
     method public String next();
+    method public void remove();
     method public void setString(String);
   }
 
@@ -49224,7 +49262,10 @@
     method public boolean containsValue(Object);
     method public void ensureCapacity(int);
     method public java.util.Set<java.util.Map.Entry<K,V>> entrySet();
+    method public boolean equals(@Nullable Object);
+    method public void forEach(java.util.function.BiConsumer<? super K,? super V>);
     method public V get(Object);
+    method public int hashCode();
     method public int indexOfKey(Object);
     method public int indexOfValue(Object);
     method public boolean isEmpty();
@@ -49236,6 +49277,7 @@
     method public V remove(Object);
     method public boolean removeAll(java.util.Collection<?>);
     method public V removeAt(int);
+    method public void replaceAll(java.util.function.BiFunction<? super K,? super V,? extends V>);
     method public boolean retainAll(java.util.Collection<?>);
     method public V setValueAt(int, V);
     method public int size();
@@ -49256,7 +49298,9 @@
     method public boolean contains(Object);
     method public boolean containsAll(java.util.Collection<?>);
     method public void ensureCapacity(int);
+    method public boolean equals(@Nullable Object);
     method public void forEach(java.util.function.Consumer<? super E>);
+    method public int hashCode();
     method public int indexOf(Object);
     method public boolean isEmpty();
     method public java.util.Iterator<E> iterator();
@@ -49264,6 +49308,7 @@
     method public boolean removeAll(android.util.ArraySet<? extends E>);
     method public boolean removeAll(java.util.Collection<?>);
     method public E removeAt(int);
+    method public boolean removeIf(java.util.function.Predicate<? super E>);
     method public boolean retainAll(java.util.Collection<?>);
     method public int size();
     method public Object[] toArray();
@@ -53118,6 +53163,7 @@
     method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
     method protected boolean drawChild(@NonNull android.graphics.Canvas, android.view.View, long);
     method public void endViewTransition(android.view.View);
+    method @Nullable public android.window.OnBackInvokedDispatcher findOnBackInvokedDispatcherForChild(@NonNull android.view.View, @NonNull android.view.View);
     method public android.view.View focusSearch(android.view.View, int);
     method public void focusableViewAvailable(android.view.View);
     method protected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams();
@@ -53159,6 +53205,7 @@
     method public void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
     method public final void offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect);
     method public final void offsetRectIntoDescendantCoords(android.view.View, android.graphics.Rect);
+    method @CallSuper public void onDescendantInvalidated(@NonNull android.view.View, @NonNull android.view.View);
     method public boolean onInterceptHoverEvent(android.view.MotionEvent);
     method public boolean onInterceptTouchEvent(android.view.MotionEvent);
     method protected abstract void onLayout(boolean, int, int, int, int);
@@ -55318,12 +55365,14 @@
     method @Nullable public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
     method @Nullable public android.os.Handler getHandler();
     method @Nullable public CharSequence getSelectedText(int);
+    method @Nullable public android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
     method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int);
     method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
     method public boolean performPrivateCommand(String, android.os.Bundle);
     method public static final void removeComposingSpans(@NonNull android.text.Spannable);
+    method public boolean replaceText(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean reportFullscreenMode(boolean);
     method public boolean requestCursorUpdates(int);
     method public boolean sendKeyEvent(android.view.KeyEvent);
@@ -55331,6 +55380,7 @@
     method public static void setComposingSpans(@NonNull android.text.Spannable);
     method public boolean setComposingText(CharSequence, int);
     method public boolean setSelection(int, int);
+    method @Nullable public android.view.inputmethod.TextSnapshot takeSnapshot();
   }
 
   public final class CompletionInfo implements android.os.Parcelable {
@@ -55665,6 +55715,7 @@
     method public boolean commitContent(android.view.inputmethod.InputContentInfo, int, android.os.Bundle);
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(CharSequence, int);
+    method public boolean commitText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean deleteSurroundingText(int, int);
     method public boolean deleteSurroundingTextInCodePoints(int, int);
     method public boolean endBatchEdit();
@@ -55673,18 +55724,29 @@
     method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
     method public android.os.Handler getHandler();
     method public CharSequence getSelectedText(int);
+    method @Nullable public android.view.inputmethod.SurroundingText getSurroundingText(int, int, int);
     method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int);
     method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int);
     method public boolean performContextMenuAction(int);
     method public boolean performEditorAction(int);
+    method public void performHandwritingGesture(@NonNull android.view.inputmethod.HandwritingGesture, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.IntConsumer);
     method public boolean performPrivateCommand(String, android.os.Bundle);
+    method public boolean performSpellCheck();
+    method public boolean previewHandwritingGesture(@NonNull android.view.inputmethod.PreviewableHandwritingGesture, @Nullable android.os.CancellationSignal);
+    method public boolean replaceText(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean reportFullscreenMode(boolean);
     method public boolean requestCursorUpdates(int);
+    method public boolean requestCursorUpdates(int, int);
+    method public void requestTextBoundsInfo(@NonNull android.graphics.RectF, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.inputmethod.TextBoundsInfoResult>);
     method public boolean sendKeyEvent(android.view.KeyEvent);
     method public boolean setComposingRegion(int, int);
+    method public boolean setComposingRegion(int, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean setComposingText(CharSequence, int);
+    method public boolean setComposingText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
+    method public boolean setImeConsumesInput(boolean);
     method public boolean setSelection(int, int);
     method public void setTarget(android.view.inputmethod.InputConnection);
+    method @Nullable public android.view.inputmethod.TextSnapshot takeSnapshot();
   }
 
   public final class InputContentInfo implements android.os.Parcelable {
@@ -57952,6 +58014,7 @@
     ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet);
     ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int);
     ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int, int);
+    method @Deprecated protected void onLayout(boolean, int, int, int, int);
   }
 
   @Deprecated public static class AbsoluteLayout.LayoutParams extends android.view.ViewGroup.LayoutParams {
@@ -58030,6 +58093,7 @@
     method public long getSelectedItemId();
     method public int getSelectedItemPosition();
     method public abstract android.view.View getSelectedView();
+    method protected void onLayout(boolean, int, int, int, int);
     method public boolean performItemClick(android.view.View, int, long);
     method public abstract void setAdapter(T);
     method public void setEmptyView(android.view.View);
@@ -58249,6 +58313,7 @@
   public abstract class BaseAdapter implements android.widget.ListAdapter android.widget.SpinnerAdapter {
     ctor public BaseAdapter();
     method public boolean areAllItemsEnabled();
+    method public CharSequence[] getAutofillOptions();
     method public android.view.View getDropDownView(int, android.view.View, android.view.ViewGroup);
     method public int getItemViewType(int);
     method public int getViewTypeCount();
@@ -58676,6 +58741,7 @@
     method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
     method @Deprecated public boolean getConsiderGoneChildrenWhenMeasuring();
     method public boolean getMeasureAllChildren();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setMeasureAllChildren(boolean);
   }
 
@@ -58729,6 +58795,7 @@
     method public boolean getUseDefaultMargins();
     method public boolean isColumnOrderPreserved();
     method public boolean isRowOrderPreserved();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setAlignmentMode(int);
     method public void setColumnCount(int);
     method public void setColumnOrderPreserved(boolean);
@@ -58951,6 +59018,7 @@
     method public float getWeightSum();
     method public boolean isBaselineAligned();
     method public boolean isMeasureWithLargestChildEnabled();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setBaselineAligned(boolean);
     method public void setBaselineAlignedChildIndex(int);
     method public void setDividerDrawable(android.graphics.drawable.Drawable);
@@ -59499,6 +59567,7 @@
     method public android.widget.RelativeLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);
     method public int getGravity();
     method public int getIgnoreGravity();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setGravity(int);
     method public void setHorizontalGravity(int);
     method public void setIgnoreGravity(int);
@@ -59953,6 +60022,7 @@
     method @Deprecated public boolean isMoving();
     method @Deprecated public boolean isOpened();
     method @Deprecated public void lock();
+    method @Deprecated protected void onLayout(boolean, int, int, int, int);
     method @Deprecated public void open();
     method @Deprecated public void setOnDrawerCloseListener(android.widget.SlidingDrawer.OnDrawerCloseListener);
     method @Deprecated public void setOnDrawerOpenListener(android.widget.SlidingDrawer.OnDrawerOpenListener);
@@ -60597,6 +60667,7 @@
     method public boolean hideOverflowMenu();
     method public void inflateMenu(@MenuRes int);
     method public boolean isOverflowMenuShowing();
+    method protected void onLayout(boolean, int, int, int, int);
     method public void setCollapseContentDescription(@StringRes int);
     method public void setCollapseContentDescription(@Nullable CharSequence);
     method public void setCollapseIcon(@DrawableRes int);
@@ -60751,7 +60822,7 @@
     method @Deprecated public android.view.View getZoomControls();
     method @Deprecated public boolean isAutoDismissed();
     method @Deprecated public boolean isVisible();
-    method public boolean onTouch(android.view.View, android.view.MotionEvent);
+    method @Deprecated public boolean onTouch(android.view.View, android.view.MotionEvent);
     method @Deprecated public void setAutoDismissed(boolean);
     method @Deprecated public void setFocusable(boolean);
     method @Deprecated public void setOnZoomListener(android.widget.ZoomButtonsController.OnZoomListener);
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
new file mode 100644
index 0000000..f1c8279
--- /dev/null
+++ b/core/api/lint-baseline.txt
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+UnflaggedApi: android.content.pm.PackageManager#FEATURE_THREAD_NETWORK:
+    New API must be flagged with @FlaggedApi: field android.content.pm.PackageManager.FEATURE_THREAD_NETWORK
+UnflaggedApi: android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM:
+    New API must be flagged with @FlaggedApi: field android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM
+UnflaggedApi: android.os.UserManager#DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO:
+    New API must be flagged with @FlaggedApi: field android.os.UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 01cf77a..1a22e9b 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android {
 
   public static final class Manifest.permission {
@@ -16,7 +14,7 @@
 
   @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public final boolean addDumpable(@NonNull android.util.Dumpable);
-    method public final boolean isResumed();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public final boolean isResumed();
   }
 
   public class ActivityManager {
diff --git a/core/api/module-lib-lint-baseline.txt b/core/api/module-lib-lint-baseline.txt
index 27436ce..16b8c42 100644
--- a/core/api/module-lib-lint-baseline.txt
+++ b/core/api/module-lib-lint-baseline.txt
@@ -57,3 +57,11 @@
     SAM-compatible parameters (such as parameter 1, "recipient", in android.os.IBinder.linkToDeath) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.os.IBinder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
     SAM-compatible parameters (such as parameter 1, "recipient", in android.os.IBinder.unlinkToDeath) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+
+
+UnflaggedApi: android.app.Activity#isResumed():
+    New API must be flagged with @FlaggedApi: method android.app.Activity.isResumed()
+UnflaggedApi: android.content.Context#REMOTE_AUTH_SERVICE:
+    New API must be flagged with @FlaggedApi: field android.content.Context.REMOTE_AUTH_SERVICE
+UnflaggedApi: android.os.IpcDataCache#MODULE_TELEPHONY:
+    New API must be flagged with @FlaggedApi: field android.os.IpcDataCache.MODULE_TELEPHONY
diff --git a/core/api/module-lib-removed.txt b/core/api/module-lib-removed.txt
index 14191eb..d802177 100644
--- a/core/api/module-lib-removed.txt
+++ b/core/api/module-lib-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/core/api/removed.txt b/core/api/removed.txt
index ede88a2..8b3696a 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.app {
 
   public class Notification implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 40eee4c..3d01011 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android {
 
   public static final class Manifest.permission {
@@ -359,6 +357,7 @@
     field public static final String SYSTEM_APPLICATION_OVERLAY = "android.permission.SYSTEM_APPLICATION_OVERLAY";
     field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
     field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
+    field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String THREAD_NETWORK_PRIVILEGED = "android.permission.THREAD_NETWORK_PRIVILEGED";
     field public static final String TIS_EXTENSION_INTERFACE = "android.permission.TIS_EXTENSION_INTERFACE";
     field public static final String TOGGLE_AUTOMOTIVE_PROJECTION = "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
     field public static final String TRIGGER_LOST_MODE = "android.permission.TRIGGER_LOST_MODE";
@@ -3483,7 +3482,7 @@
     field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
     field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
     field public static final String TETHERING_SERVICE = "tethering";
-    field public static final String THREAD_NETWORK_SERVICE = "thread_network";
+    field @FlaggedApi("com.android.net.thread.flags.thread_enabled") public static final String THREAD_NETWORK_SERVICE = "thread_network";
     field public static final String TIME_MANAGER_SERVICE = "time_manager";
     field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
     field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
@@ -4042,7 +4041,9 @@
   }
 
   public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
+    method public int describeContents();
     method public void onUninstallComplete(@NonNull String, int, @Nullable String);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR;
   }
 
@@ -5941,6 +5942,7 @@
 
   public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
     ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
+    method public int describeContents();
     method public int getId();
     method @NonNull public java.util.Locale getLocale();
     method public int getRecognitionModes();
@@ -5963,6 +5965,7 @@
   public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
     ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);
     ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]);
+    method public int describeContents();
     method @NonNull public android.hardware.soundtrigger.SoundTrigger.Keyphrase[] getKeyphrases();
     method @NonNull public static android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel readFromParcel(@NonNull android.os.Parcel);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -5970,6 +5973,7 @@
   }
 
   public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
+    method public int describeContents();
     method public int getEnd();
     method public int getStart();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -7412,6 +7416,7 @@
 
   public abstract class MusicRecognitionService extends android.app.Service {
     ctor public MusicRecognitionService();
+    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback);
   }
 
@@ -7470,6 +7475,7 @@
 
   public abstract class SoundTriggerDetectionService extends android.app.Service {
     ctor public SoundTriggerDetectionService();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method @MainThread public void onConnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
     method @MainThread public void onDisconnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);
     method @MainThread public void onError(@NonNull java.util.UUID, @Nullable android.os.Bundle, int, int);
@@ -8183,6 +8189,7 @@
   }
 
   public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method protected void finalize();
     method public long getAudioHandle();
     method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
     method public long getAvDataId();
@@ -9577,6 +9584,8 @@
 package android.net.metrics {
 
   @Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class ApfProgramEvent.Builder {
@@ -9591,6 +9600,8 @@
   }
 
   @Deprecated public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class ApfStats.Builder {
@@ -9609,6 +9620,8 @@
   }
 
   @Deprecated public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class DhcpClientEvent.Builder {
@@ -9620,7 +9633,9 @@
 
   @Deprecated public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public DhcpErrorEvent(int);
+    method @Deprecated public int describeContents();
     method @Deprecated public static int errorCodeWithOption(int, int);
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int BOOTP_TOO_SHORT = 67174400; // 0x4010000
     field @Deprecated public static final int BUFFER_UNDERFLOW = 83951616; // 0x5010000
     field @Deprecated public static final int DHCP_BAD_MAGIC_COOKIE = 67239936; // 0x4020000
@@ -9658,6 +9673,8 @@
 
   @Deprecated public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public IpManagerEvent(int, long);
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int COMPLETE_LIFECYCLE = 3; // 0x3
     field @Deprecated public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8
     field @Deprecated public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7
@@ -9670,6 +9687,8 @@
 
   @Deprecated public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public IpReachabilityEvent(int);
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int NUD_FAILED = 512; // 0x200
     field @Deprecated public static final int NUD_FAILED_ORGANIC = 1024; // 0x400
     field @Deprecated public static final int PROBE = 256; // 0x100
@@ -9680,6 +9699,8 @@
   @Deprecated public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {
     ctor @Deprecated public NetworkEvent(int, long);
     ctor @Deprecated public NetworkEvent(int);
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4
     field @Deprecated public static final int NETWORK_CONNECTED = 1; // 0x1
     field @Deprecated public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc
@@ -9696,6 +9717,8 @@
   }
 
   @Deprecated public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
   }
 
   @Deprecated public static final class RaEvent.Builder {
@@ -9710,7 +9733,9 @@
   }
 
   @Deprecated public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event {
+    method @Deprecated public int describeContents();
     method @Deprecated @NonNull public static String getProbeName(int);
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public static final int DNS_FAILURE = 0; // 0x0
     field @Deprecated public static final int DNS_SUCCESS = 1; // 0x1
     field @Deprecated public static final int PROBE_DNS = 0; // 0x0
@@ -10220,6 +10245,7 @@
     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();
@@ -10227,8 +10253,10 @@
     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
@@ -10276,6 +10304,7 @@
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean hasCategory(@NonNull String);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean isOnHost();
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean isOtherServiceEnabled();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadAppLabel(@NonNull android.content.pm.PackageManager);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadBanner(@NonNull android.content.pm.PackageManager);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
@@ -10286,10 +10315,15 @@
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOffHostSecureElement(@NonNull String);
+    method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOtherServiceEnabled(boolean);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
     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();
@@ -10764,6 +10798,7 @@
     ctor public ParcelableHolder(int);
     method public int describeContents();
     method @Nullable public <T extends android.os.Parcelable> T getParcelable(@NonNull Class<T>);
+    method public int getStability();
     method public void readFromParcel(@NonNull android.os.Parcel);
     method public void setParcelable(@Nullable android.os.Parcelable);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -12108,6 +12143,7 @@
 
   public abstract class FieldClassificationService extends android.app.Service {
     ctor public FieldClassificationService();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onClassificationRequest(@NonNull android.service.assist.classification.FieldClassificationRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.assist.classification.FieldClassificationResponse,java.lang.Exception>);
     method public void onConnected();
     method public void onDisconnected();
@@ -12186,6 +12222,7 @@
     method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
     method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]);
     method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
     method public void onDisconnected();
     method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback);
@@ -12224,6 +12261,7 @@
 
   public final class FillWindow implements java.lang.AutoCloseable {
     ctor public FillWindow();
+    method public void close();
     method public void destroy();
     method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);
   }
@@ -12249,6 +12287,7 @@
   public final class CarrierMessagingServiceWrapper implements java.lang.AutoCloseable {
     ctor public CarrierMessagingServiceWrapper();
     method public boolean bindToCarrierMessagingService(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull Runnable);
+    method public void close();
     method public void disconnect();
     method public void downloadMms(@NonNull android.net.Uri, int, @NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
     method public void receiveSms(@NonNull android.service.carrier.MessagePdu, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);
@@ -12299,6 +12338,7 @@
     method public final void disableSelf();
     method public void onActivityEvent(@NonNull android.service.contentcapture.ActivityEvent);
     method public void onActivitySnapshot(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.service.contentcapture.SnapshotData);
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConnected();
     method public void onContentCaptureEvent(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.view.contentcapture.ContentCaptureEvent);
     method public void onCreateContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext, @NonNull android.view.contentcapture.ContentCaptureSessionId);
@@ -12337,6 +12377,7 @@
 
   public abstract class ContentSuggestionsService extends android.app.Service {
     ctor public ContentSuggestionsService();
+    method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onClassifyContentSelections(@NonNull android.app.contentsuggestions.ClassificationsRequest, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);
     method public abstract void onNotifyInteraction(@NonNull String, @NonNull android.os.Bundle);
     method public abstract void onProcessContextImage(int, @Nullable android.graphics.Bitmap, @NonNull android.os.Bundle);
@@ -12350,6 +12391,7 @@
 
   public abstract class DataLoaderService extends android.app.Service {
     ctor public DataLoaderService();
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams);
   }
 
@@ -13022,6 +13064,7 @@
 
   public class TraceReportService extends android.app.Service {
     ctor public TraceReportService();
+    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
   }
 
@@ -13610,6 +13653,7 @@
     method @Nullable public android.content.ComponentName getCallScreeningComponent();
     method public boolean isBlocked();
     method public boolean isInContacts();
+    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.Connection.CallFilteringCompletionInfo> CREATOR;
   }
 
@@ -13918,6 +13962,7 @@
     method public int getReason();
     method public int getTimeoutSeconds();
     method public boolean isEnabled();
+    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
     field public static final int REASON_ALL = 4; // 0x4
     field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
@@ -14202,6 +14247,7 @@
     method public int getDownlinkCapacityKbps();
     method public int getType();
     method public int getUplinkCapacityKbps();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LinkCapacityEstimate> CREATOR;
     field public static final int INVALID = -1; // 0xffffffff
     field public static final int LCE_TYPE_COMBINED = 2; // 0x2
@@ -14211,8 +14257,10 @@
 
   public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo {
     ctor public LteVopsSupportInfo(int, int);
+    method public boolean equals(@Nullable Object);
     method public int getEmcBearerSupport();
     method public int getVopsSupport();
+    method public int hashCode();
     method public boolean isEmergencyServiceFallbackSupported();
     method public boolean isEmergencyServiceSupported();
     method public boolean isVopsSupported();
@@ -14312,9 +14360,11 @@
 
   public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo {
     ctor public NrVopsSupportInfo(int, int, int);
+    method public boolean equals(@Nullable Object);
     method public int getEmcSupport();
     method public int getEmfSupport();
     method public int getVopsSupport();
+    method public int hashCode();
     method public boolean isEmergencyServiceFallbackSupported();
     method public boolean isEmergencyServiceSupported();
     method public boolean isVopsSupported();
@@ -15427,6 +15477,7 @@
 
   public abstract class QualifiedNetworksService extends android.app.Service {
     ctor public QualifiedNetworksService();
+    method public android.os.IBinder onBind(android.content.Intent);
     method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int);
     field public static final String QUALIFIED_NETWORKS_SERVICE_INTERFACE = "android.telephony.data.QualifiedNetworksService";
   }
@@ -15618,6 +15669,7 @@
   public class GbaService extends android.app.Service {
     ctor public GbaService();
     method public void onAuthenticationRequest(int, int, int, @NonNull android.net.Uri, @NonNull byte[], boolean);
+    method public android.os.IBinder onBind(android.content.Intent);
     method public final void reportAuthenticationFailure(int, int) throws java.lang.RuntimeException;
     method public final void reportKeysAvailable(int, @NonNull byte[], @NonNull String) throws java.lang.RuntimeException;
     field public static final String SERVICE_INTERFACE = "android.telephony.gba.GbaService";
@@ -16120,6 +16172,7 @@
     method @Deprecated public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int);
     method @NonNull public android.telephony.ims.stub.ImsRegistrationImplBase getRegistrationForSubscription(int, int);
     method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int);
+    method public android.os.IBinder onBind(android.content.Intent);
     method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException;
     method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures();
     method public void readyForFeatureCreation();
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 71c02dc..1e9e9be 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -226,6 +226,11 @@
 SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams):
     SAM-compatible parameters (such as parameter 2, "filePathCallback", in android.webkit.WebChromeClient.onShowFileChooser) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 
+
+UnflaggedApi: android.Manifest.permission#REGISTER_NSD_OFFLOAD_ENGINE:
+    New API must be flagged with @FlaggedApi: field android.Manifest.permission.REGISTER_NSD_OFFLOAD_ENGINE
+UnflaggedApi: android.content.Context#THREAD_NETWORK_SERVICE:
+    New API must be flagged with @FlaggedApi: field android.content.Context.THREAD_NETWORK_SERVICE
 UnflaggedApi: android.nfc.cardemulation.AidGroup#CONTENTS_FILE_DESCRIPTOR:
     New API must be flagged with @FlaggedApi: field android.nfc.cardemulation.AidGroup.CONTENTS_FILE_DESCRIPTOR
 UnflaggedApi: android.nfc.cardemulation.AidGroup#PARCELABLE_WRITE_RETURN_VALUE:
@@ -238,3 +243,7 @@
     New API must be flagged with @FlaggedApi: field android.nfc.cardemulation.NfcFServiceInfo.CONTENTS_FILE_DESCRIPTOR
 UnflaggedApi: android.nfc.cardemulation.NfcFServiceInfo#PARCELABLE_WRITE_RETURN_VALUE:
     New API must be flagged with @FlaggedApi: field android.nfc.cardemulation.NfcFServiceInfo.PARCELABLE_WRITE_RETURN_VALUE
+UnflaggedApi: android.telephony.mbms.vendor.MbmsDownloadServiceBase#DESCRIPTOR:
+    New API must be flagged with @FlaggedApi: field android.telephony.mbms.vendor.MbmsDownloadServiceBase.DESCRIPTOR
+UnflaggedApi: android.telephony.mbms.vendor.MbmsStreamingServiceBase#DESCRIPTOR:
+    New API must be flagged with @FlaggedApi: field android.telephony.mbms.vendor.MbmsStreamingServiceBase.DESCRIPTOR
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 4161330..1c10356 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.app {
 
   public class AppOpsManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8d0e396..7742b86 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android {
 
   public static final class Manifest.permission {
@@ -3932,7 +3930,9 @@
 package android.window {
 
   public final class BackNavigationInfo implements android.os.Parcelable {
+    method public int describeContents();
     method @NonNull public static String typeToString(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.BackNavigationInfo> CREATOR;
     field public static final String KEY_TRIGGER_BACK = "TriggerBack";
     field public static final int TYPE_CALLBACK = 4; // 0x4
@@ -3993,11 +3993,13 @@
   }
 
   public final class TaskFragmentCreationParams implements android.os.Parcelable {
+    method public int describeContents();
     method @NonNull public android.os.IBinder getFragmentToken();
     method @NonNull public android.graphics.Rect getInitialRelativeBounds();
     method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
     method @NonNull public android.os.IBinder getOwnerToken();
     method public int getWindowingMode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;
   }
 
@@ -4009,6 +4011,7 @@
   }
 
   public final class TaskFragmentInfo implements android.os.Parcelable {
+    method public int describeContents();
     method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);
     method @NonNull public java.util.List<android.os.IBinder> getActivities();
     method @NonNull public java.util.List<android.os.IBinder> getActivitiesRequestedInTaskFragment();
@@ -4022,6 +4025,7 @@
     method public boolean isEmpty();
     method public boolean isTaskClearedForReuse();
     method public boolean isVisible();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;
   }
 
@@ -4044,6 +4048,8 @@
   }
 
   public final class TaskFragmentOrganizerToken implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
   }
 
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index cf02643..98f492f 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -145,6 +145,10 @@
     Intent action constant name must be ACTION_FOO: SMS_CARRIER_PROVISION_ACTION
 
 
+KotlinKeyword: android.app.Notification#when:
+    Avoid field names that are Kotlin hard keywords ("when"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords
+
+
 KotlinOperator: android.os.PackageTagsList#contains(android.os.PackageTagsList):
     Method can be invoked as a "in" operator from Kotlin: `contains` (this is usually desirable; just make sure it makes sense for this type of object)
 KotlinOperator: android.util.SparseArrayMap#get(int, K):
@@ -761,6 +765,8 @@
     Missing nullability on method `getMinuteView` return
 MissingNullability: android.widget.TimePicker#getPmView():
     Missing nullability on method `getPmView` return
+MissingNullability: android.window.TaskFragmentOrganizerToken#writeToParcel(android.os.Parcel, int) parameter #0:
+    Missing nullability on parameter `dest` in method `writeToParcel`
 
 
 MutableBareField: android.content.AutofillOptions#appDisabledExpiration:
@@ -963,16 +969,100 @@
     Methods calling system APIs should rethrow `RemoteException` as `RuntimeException` (but do not list it in the throws clause)
 
 
+SamShouldBeLast: android.animation.ValueAnimator#ofObject(android.animation.TypeEvaluator, java.lang.Object...):
+    SAM-compatible parameters (such as parameter 1, "evaluator", in android.animation.ValueAnimator.ofObject) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.Activity#convertToTranslucent(android.app.Activity.TranslucentConversionListener, android.app.ActivityOptions):
+    SAM-compatible parameters (such as parameter 1, "callback", in android.app.Activity.convertToTranslucent) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.ActivityManager#addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.app.ActivityManager.addOnUidImportanceListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.AlarmManager#set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 4, "listener", in android.app.AlarmManager.set) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.AlarmManager#set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource):
+    SAM-compatible parameters (such as parameter 5, "listener", in android.app.AlarmManager.set) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.AlarmManager#setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 4, "listener", in android.app.AlarmManager.setExact) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 5, "listener", in android.app.AlarmManager.setWindow) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.PendingIntent#send(android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished, android.os.Handler):
+    SAM-compatible parameters (such as parameter 4, "onFinished", in android.app.PendingIntent.send) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.PendingIntent#send(android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished, android.os.Handler, String):
+    SAM-compatible parameters (such as parameter 4, "onFinished", in android.app.PendingIntent.send) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.PendingIntent#send(android.content.Context, int, android.content.Intent, android.app.PendingIntent.OnFinished, android.os.Handler, String, android.os.Bundle):
+    SAM-compatible parameters (such as parameter 4, "onFinished", in android.app.PendingIntent.send) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.PendingIntent#send(int, android.app.PendingIntent.OnFinished, android.os.Handler):
+    SAM-compatible parameters (such as parameter 2, "onFinished", in android.app.PendingIntent.send) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.UiAutomation#executeAndWaitForEvent(Runnable, android.app.UiAutomation.AccessibilityEventFilter, long):
+    SAM-compatible parameters (such as parameter 2, "filter", in android.app.UiAutomation.executeAndWaitForEvent) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.app.WallpaperManager#addOnColorsChangedListener(android.app.WallpaperManager.OnColorsChangedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.app.WallpaperManager.addOnColorsChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.content.pm.ActivityInfo#dump(android.util.Printer, String):
+    SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.ActivityInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.content.pm.ApplicationInfo#dump(android.util.Printer, String):
+    SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.ApplicationInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.content.pm.ComponentInfo#dumpBack(android.util.Printer, String):
+    SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.ComponentInfo.dumpBack) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.content.pm.ComponentInfo#dumpFront(android.util.Printer, String):
+    SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.ComponentInfo.dumpFront) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.content.pm.PackageItemInfo#dumpBack(android.util.Printer, String):
+    SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.PackageItemInfo.dumpBack) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.content.pm.PackageItemInfo#dumpFront(android.util.Printer, String):
+    SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.PackageItemInfo.dumpFront) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.database.sqlite.SQLiteCursorDriver#query(android.database.sqlite.SQLiteDatabase.CursorFactory, String[]):
+    SAM-compatible parameters (such as parameter 1, "factory", in android.database.sqlite.SQLiteCursorDriver.query) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.database.sqlite.SQLiteDebug#dump(android.util.Printer, String[]):
     SAM-compatible parameters (such as parameter 1, "printer", in android.database.sqlite.SQLiteDebug.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.database.sqlite.SQLiteDirectCursorDriver#query(android.database.sqlite.SQLiteDatabase.CursorFactory, String[]):
     SAM-compatible parameters (such as parameter 1, "factory", in android.database.sqlite.SQLiteDirectCursorDriver.query) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.graphics.drawable.AdaptiveIconDrawable#scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long):
+    SAM-compatible parameters (such as parameter 2, "what", in android.graphics.drawable.AdaptiveIconDrawable.scheduleDrawable) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.graphics.drawable.Drawable#scheduleSelf(Runnable, long):
+    SAM-compatible parameters (such as parameter 1, "what", in android.graphics.drawable.Drawable.scheduleSelf) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.graphics.drawable.Drawable.Callback#scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long):
+    SAM-compatible parameters (such as parameter 2, "what", in android.graphics.drawable.Drawable.Callback.scheduleDrawable) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.graphics.drawable.LayerDrawable#scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long):
+    SAM-compatible parameters (such as parameter 2, "what", in android.graphics.drawable.LayerDrawable.scheduleDrawable) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.location.LocationManager.addNmeaListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, android.location.LocationListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 4, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(String, android.location.LocationListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.location.LocationManager#requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper):
+    SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestSingleUpdate) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioManager#abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes):
+    SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.abandonAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int):
+    SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.requestAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy):
+    SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.requestAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
+    SAM-compatible parameters (such as parameter 1, "l", in android.media.AudioManager.requestAudioFocus) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioRecord#addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioRecord.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioRecord#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioRecord.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioTrack.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.media.AudioTrack.addOnRoutingChangedListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.media.MediaCas#setEventListener(android.media.MediaCas.EventListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.media.MediaCas.setEventListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.os.Parcel#createFixedArray(Class<T>, java.util.function.Function<android.os.IBinder,S>, int...):
+    SAM-compatible parameters (such as parameter 2, "asInterface", in android.os.Parcel.createFixedArray) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.os.StrictMode.ViolationInfo#dump(android.util.Printer, String):
     SAM-compatible parameters (such as parameter 1, "pw", in android.os.StrictMode.ViolationInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.permission.PermissionControllerManager#countPermissionApps(java.util.List<java.lang.String>, int, android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, android.os.Handler):
     SAM-compatible parameters (such as parameter 3, "callback", in android.permission.PermissionControllerManager.countPermissionApps) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.permission.PermissionControllerManager#getAppPermissions(String, android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, android.os.Handler):
     SAM-compatible parameters (such as parameter 2, "callback", in android.permission.PermissionControllerManager.getAppPermissions) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.security.KeyChain#choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, String[], java.security.Principal[], String, int, String):
+    SAM-compatible parameters (such as parameter 2, "response", in android.security.KeyChain.choosePrivateKeyAlias) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.security.KeyChain#choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, String[], java.security.Principal[], android.net.Uri, String):
+    SAM-compatible parameters (such as parameter 2, "response", in android.security.KeyChain.choosePrivateKeyAlias) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.service.autofill.CharSequenceTransformation#apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int):
     SAM-compatible parameters (such as parameter 1, "finder", in android.service.autofill.CharSequenceTransformation.apply) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.service.autofill.DateTransformation#apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int):
@@ -985,8 +1075,24 @@
     SAM-compatible parameters (such as parameter 2, "action", in android.view.Choreographer.postCallback) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.view.Choreographer#postCallbackDelayed(int, Runnable, Object, long):
     SAM-compatible parameters (such as parameter 2, "action", in android.view.Choreographer.postCallbackDelayed) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.view.Choreographer#postFrameCallbackDelayed(android.view.Choreographer.FrameCallback, long):
+    SAM-compatible parameters (such as parameter 1, "callback", in android.view.Choreographer.postFrameCallbackDelayed) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.view.Choreographer#removeCallbacks(int, Runnable, Object):
     SAM-compatible parameters (such as parameter 2, "action", in android.view.Choreographer.removeCallbacks) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.view.View#postDelayed(Runnable, long):
+    SAM-compatible parameters (such as parameter 1, "action", in android.view.View.postDelayed) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.view.View#postOnAnimationDelayed(Runnable, long):
+    SAM-compatible parameters (such as parameter 1, "action", in android.view.View.postOnAnimationDelayed) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.view.View#scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long):
+    SAM-compatible parameters (such as parameter 2, "what", in android.view.View.scheduleDrawable) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.view.Window#addOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.view.Window.addOnFrameMetricsAvailableListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.view.accessibility.AccessibilityManager#addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.view.accessibility.AccessibilityManager.addAccessibilityStateChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 1, "listener", in android.view.accessibility.AccessibilityManager.addTouchExplorationStateChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.view.inputmethod.InputMethodInfo#dump(android.util.Printer, String):
+    SAM-compatible parameters (such as parameter 1, "pw", in android.view.inputmethod.InputMethodInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 
 
 StaticUtils: android.os.health.HealthKeys:
@@ -1003,6 +1109,48 @@
     Methods accepting `File` should also accept `FileDescriptor` or streams: method android.os.FileUtils.contains(java.io.File,java.io.File)
 
 
+UnflaggedApi: android.app.admin.DevicePolicyIdentifiers#PERMITTED_INPUT_METHODS_POLICY:
+    New API must be flagged with @FlaggedApi: field android.app.admin.DevicePolicyIdentifiers.PERMITTED_INPUT_METHODS_POLICY
+UnflaggedApi: android.app.admin.DevicePolicyIdentifiers#PERSONAL_APPS_SUSPENDED_POLICY:
+    New API must be flagged with @FlaggedApi: field android.app.admin.DevicePolicyIdentifiers.PERSONAL_APPS_SUSPENDED_POLICY
+UnflaggedApi: android.app.admin.DevicePolicyIdentifiers#SCREEN_CAPTURE_DISABLED_POLICY:
+    New API must be flagged with @FlaggedApi: field android.app.admin.DevicePolicyIdentifiers.SCREEN_CAPTURE_DISABLED_POLICY
+UnflaggedApi: android.media.soundtrigger.SoundTriggerInstrumentation#setInPhoneCallState(boolean):
+    New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerInstrumentation.setInPhoneCallState(boolean)
+UnflaggedApi: android.media.soundtrigger.SoundTriggerManager#createManagerForModule(android.hardware.soundtrigger.SoundTrigger.ModuleProperties):
+    New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.createManagerForModule(android.hardware.soundtrigger.SoundTrigger.ModuleProperties)
+UnflaggedApi: android.media.soundtrigger.SoundTriggerManager#createManagerForTestModule():
+    New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.createManagerForTestModule()
+UnflaggedApi: android.media.soundtrigger.SoundTriggerManager#listModuleProperties():
+    New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.listModuleProperties()
+UnflaggedApi: android.media.soundtrigger.SoundTriggerManager#loadSoundModel(android.hardware.soundtrigger.SoundTrigger.SoundModel):
+    New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.loadSoundModel(android.hardware.soundtrigger.SoundTrigger.SoundModel)
+UnflaggedApi: android.media.soundtrigger.SoundTriggerManager.Model#getSoundModel():
+    New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.Model.getSoundModel()
+UnflaggedApi: android.os.BatteryManager#BATTERY_PLUGGED_ANY:
+    New API must be flagged with @FlaggedApi: field android.os.BatteryManager.BATTERY_PLUGGED_ANY
+UnflaggedApi: android.os.UserHandle#USER_CURRENT:
+    New API must be flagged with @FlaggedApi: field android.os.UserHandle.USER_CURRENT
+UnflaggedApi: android.os.UserManager#getAliveUsers():
+    New API must be flagged with @FlaggedApi: method android.os.UserManager.getAliveUsers()
+UnflaggedApi: android.os.UserManager#getUsers():
+    New API must be flagged with @FlaggedApi: method android.os.UserManager.getUsers()
+UnflaggedApi: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities:
+    New API must be flagged with @FlaggedApi: class android.telephony.ims.feature.MmTelFeature.MmTelCapabilities
+UnflaggedApi: android.view.WindowManager.LayoutParams#preferredMaxDisplayRefreshRate:
+    New API must be flagged with @FlaggedApi: field android.view.WindowManager.LayoutParams.preferredMaxDisplayRefreshRate
+UnflaggedApi: android.view.WindowManager.LayoutParams#preferredMinDisplayRefreshRate:
+    New API must be flagged with @FlaggedApi: field android.view.WindowManager.LayoutParams.preferredMinDisplayRefreshRate
+UnflaggedApi: android.view.inputmethod.InputMethodManager#isCurrentRootView(android.view.View):
+    New API must be flagged with @FlaggedApi: method android.view.inputmethod.InputMethodManager.isCurrentRootView(android.view.View)
+UnflaggedApi: android.view.inputmethod.InsertModeGesture:
+    New API must be flagged with @FlaggedApi: class android.view.inputmethod.InsertModeGesture
+UnflaggedApi: android.window.WindowInfosListenerForTest.WindowInfo#displayId:
+    New API must be flagged with @FlaggedApi: field android.window.WindowInfosListenerForTest.WindowInfo.displayId
+UnflaggedApi: android.window.WindowInfosListenerForTest.WindowInfo#isVisible:
+    New API must be flagged with @FlaggedApi: field android.window.WindowInfosListenerForTest.WindowInfo.isVisible
+
+
 UseIcu: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getKeyphraseMetadata(String, java.util.Locale) parameter #1:
     Type `java.util.Locale` should be replaced with richer ICU type `android.icu.util.ULocale`
 UseIcu: android.hardware.soundtrigger.KeyphraseEnrollmentInfo#getManageKeyphraseIntent(int, String, java.util.Locale) parameter #2:
diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt
index 14191eb..d802177 100644
--- a/core/api/test-removed.txt
+++ b/core/api/test-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 70864d5..b6789aa 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -14,6 +14,15 @@
     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: [
@@ -217,7 +226,6 @@
         "com/android/internal/util/ConcurrentUtils.java",
         "com/android/internal/util/DumpUtils.java",
         "com/android/internal/util/FastPrintWriter.java",
-        "com/android/internal/util/FastXmlSerializer.java",
         "com/android/internal/util/FunctionalUtils.java",
         "com/android/internal/util/ParseUtils.java",
         "com/android/internal/util/RingBufferIndices.java",
@@ -446,7 +454,6 @@
         "com/android/internal/util/AsyncChannel.java",
         "com/android/internal/util/AsyncService.java",
         "com/android/internal/util/BitwiseInputStream.java",
-        "com/android/internal/util/FastXmlSerializer.java",
         "com/android/internal/util/HexDump.java",
         "com/android/internal/util/IndentingPrintWriter.java",
         "com/android/internal/util/UserIcons.java",
@@ -496,7 +503,6 @@
         "android/net/InterfaceConfiguration.java",
         "android/util/BackupUtils.java",
         "android/util/Rational.java",
-        "com/android/internal/util/FastXmlSerializer.java",
         "com/android/internal/util/HexDump.java",
         "com/android/internal/util/MessageUtils.java",
         "com/android/internal/util/WakeupMessage.java",
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2590869..06c139f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -31,6 +31,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.IdRes;
 import android.annotation.IntDef;
 import android.annotation.LayoutRes;
@@ -79,6 +80,7 @@
 import android.media.AudioManager;
 import android.media.session.MediaController;
 import android.net.Uri;
+import android.nfc.Flags;
 import android.os.BadParcelableException;
 import android.os.Build;
 import android.os.Bundle;
@@ -8915,6 +8917,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public final boolean isResumed() {
         return mResumed;
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 6a51171..2d55403 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -115,6 +115,26 @@
             "android.app.action.CONFIRM_REMOTE_DEVICE_CREDENTIAL";
 
     /**
+     * Intent used to prompt user for device credential for entering repair
+     * mode. If the credential is verified successfully, then the information
+     * needed to verify the credential again will be written to a location that
+     * is available to repair mode. This makes it possible for repair mode to
+     * require that the same credential be provided to exit repair mode.
+     * @hide
+     */
+    public static final String ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL =
+            "android.app.action.PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL";
+
+    /**
+     * Intent used to prompt user for device credential that is written by
+     * {@link #ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL} for exiting
+     * repair mode.
+     * @hide
+     */
+    public static final String ACTION_CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL =
+            "android.app.action.CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL";
+
+    /**
      * A CharSequence dialog title to show to the user when used with a
      * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
      * @hide
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index e1c45d9..164bdbe 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -36,6 +36,7 @@
 per-file GameState* = file:/GAME_MANAGER_OWNERS
 per-file IGameManager* = file:/GAME_MANAGER_OWNERS
 per-file IGameMode* = file:/GAME_MANAGER_OWNERS
+per-file BackgroundStartPrivileges.java = file:/BAL_OWNERS
 
 # ActivityThread
 per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 4f5da99..325758b 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -113,6 +113,7 @@
 import android.hardware.lights.LightsManager;
 import android.hardware.lights.SystemLightsManager;
 import android.hardware.location.ContextHubManager;
+import android.hardware.location.IContextHubService;
 import android.hardware.radio.RadioManager;
 import android.hardware.usb.IUsbManager;
 import android.hardware.usb.UsbManager;
@@ -1125,8 +1126,12 @@
                 new CachedServiceFetcher<ContextHubManager>() {
             @Override
             public ContextHubManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                return new ContextHubManager(ctx.getOuterContext(),
-                  ctx.mMainThread.getHandler().getLooper());
+                IBinder b = ServiceManager.getService(Context.CONTEXTHUB_SERVICE);
+                if (b == null) {
+                    return null;
+                }
+                return new ContextHubManager(IContextHubService.Stub.asInterface(b),
+                        ctx.mMainThread.getHandler().getLooper());
             }});
 
         registerService(Context.INCIDENT_SERVICE, IncidentManager.class,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index da5e40a..f3cc594 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16111,11 +16111,6 @@
      * Called by a profile owner of an organization-owned managed profile to suspend personal
      * apps on the device. When personal apps are suspended the device can only be used for calls.
      *
-     * <p>When personal apps are suspended, an ongoing notification about that is shown to the user.
-     * When the user taps the notification, system invokes {@link #ACTION_CHECK_POLICY_COMPLIANCE}
-     * in the profile owner package. Profile owner implementation that uses personal apps suspension
-     * must handle this intent.
-     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with
      * @param suspended Whether personal apps should be suspended.
      * @throws IllegalStateException if the profile owner doesn't have an activity that handles
diff --git a/core/java/android/app/contentsuggestions/OWNERS b/core/java/android/app/contentsuggestions/OWNERS
index cf54c2a..5f8de77 100644
--- a/core/java/android/app/contentsuggestions/OWNERS
+++ b/core/java/android/app/contentsuggestions/OWNERS
@@ -1,7 +1,4 @@
 # Bug component: 643919
 
-augale@google.com
-joannechung@google.com
-markpun@google.com
-lpeter@google.com
-tymtsai@google.com
+hackz@google.com
+volnov@google.com
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index cd45f4d..b4f4a7e 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -212,6 +212,11 @@
     }
 
     /** @hide */
+    public AttributionSource withDefaultToken() {
+        return withToken(sDefaultToken);
+    }
+
+    /** @hide */
     public AttributionSource withPid(int pid) {
         return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
                 getToken(), mAttributionSourceState.renouncedPermissions, getNext());
@@ -520,16 +525,28 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         AttributionSource that = (AttributionSource) o;
-        return mAttributionSourceState.uid == that.mAttributionSourceState.uid
+        return equalsExceptToken(that) && Objects.equals(
+                mAttributionSourceState.token, that.mAttributionSourceState.token);
+    }
+
+    /**
+     * We store trusted attribution sources without their token (the token is the key to the map)
+     * to avoid having a strong reference to the token. This means, when checking the equality of a
+     * supplied AttributionSource in PermissionManagerService.isTrustedAttributionSource, we want to
+     * compare everything except the token.
+     *
+     * @hide
+     */
+    public boolean equalsExceptToken(@Nullable AttributionSource o) {
+        if (o == null) return false;
+        return mAttributionSourceState.uid == o.mAttributionSourceState.uid
                 && Objects.equals(mAttributionSourceState.packageName,
-                        that.mAttributionSourceState.packageName)
+                o.mAttributionSourceState.packageName)
                 && Objects.equals(mAttributionSourceState.attributionTag,
-                        that.mAttributionSourceState.attributionTag)
-                && Objects.equals(mAttributionSourceState.token,
-                        that.mAttributionSourceState.token)
+                o.mAttributionSourceState.attributionTag)
                 && Arrays.equals(mAttributionSourceState.renouncedPermissions,
-                        that.mAttributionSourceState.renouncedPermissions)
-                && Objects.equals(getNext(), that.getNext());
+                o.mAttributionSourceState.renouncedPermissions)
+                && Objects.equals(getNext(), o.getNext());
     }
 
     @Override
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 10857e5..3d43845 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -23,6 +23,7 @@
 import android.annotation.ColorRes;
 import android.annotation.DisplayContext;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.NonNull;
@@ -4766,6 +4767,7 @@
      * @see android.net.thread.ThreadNetworkManager
      * @hide
      */
+    @FlaggedApi("com.android.net.thread.flags.thread_enabled")
     @SystemApi
     public static final String THREAD_NETWORK_SERVICE = "thread_network";
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c3fd744..0fd0e15 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1161,6 +1161,11 @@
      * numbers.  Applications can <strong>dial</strong> emergency numbers using
      * {@link #ACTION_DIAL}, however.
      *
+     * <p>Note: This Intent can only be used to dial call forwarding MMI codes if the application
+     * using this intent is set as the default or system dialer. The system will treat any other
+     * application using this Intent for the purpose of dialing call forwarding MMI codes as if the
+     * {@link #ACTION_DIAL} Intent was used instead.
+     *
      * <p>Note: An app filling the {@link android.app.role.RoleManager#ROLE_DIALER} role should use
      * {@link android.telecom.TelecomManager#placeCall(Uri, Bundle)} to place calls rather than
      * relying on this intent.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 02650c6..f07a23f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -20,6 +20,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.CheckResult;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.LongDef;
@@ -1449,6 +1450,7 @@
             INSTALL_ALLOW_DOWNGRADE,
             INSTALL_STAGED,
             INSTALL_REQUEST_UPDATE_OWNERSHIP,
+            INSTALL_IGNORE_DEXOPT_PROFILE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InstallFlags {}
@@ -1674,6 +1676,18 @@
     public static final int INSTALL_FROM_MANAGED_USER_OR_PROFILE = 1 << 26;
 
     /**
+     * If set, all dexopt profiles are ignored by dexopt during the installation, including the
+     * profile in the DM file and the profile embedded in the APK file. If an invalid profile is
+     * provided during installation, no warning will be reported by {@code adb install}.
+     *
+     * This option does not affect later dexopt operations (e.g., background dexopt and manual `pm
+     * compile` invocations).
+     *
+     * @hide
+     */
+    public static final int INSTALL_IGNORE_DEXOPT_PROFILE = 1 << 28;
+
+    /**
      * Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is
      * a development-only feature and should not be used on end user devices.
      *
@@ -3638,6 +3652,7 @@
      * The device is capable of communicating with other devices via
      * <a href="https://www.threadgroup.org">Thread</a> networking protocol.
      */
+    @FlaggedApi("com.android.net.thread.flags.thread_enabled")
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
 
diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl
index 1333f0d..a9742cb 100644
--- a/core/java/android/hardware/HardwareBuffer.aidl
+++ b/core/java/android/hardware/HardwareBuffer.aidl
@@ -16,4 +16,4 @@
 
 package android.hardware;
 
-@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h" rust_type "nativewindow::HardwareBuffer";
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 943eee4..61d8702 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -18,6 +18,7 @@
 
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -296,4 +297,15 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({BIOMETRIC_LOCKOUT_NONE, BIOMETRIC_LOCKOUT_TIMED, BIOMETRIC_LOCKOUT_PERMANENT})
     @interface LockoutMode {}
+
+    //
+    // Other miscellaneous constants
+    //
+
+    /**
+     * Returned from {@link BiometricManager#getLastAuthenticationTime(int)} when there has
+     * been no successful authentication for the given authenticator since boot.
+     */
+    @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME)
+    long BIOMETRIC_NO_AUTHENTICATION = -1;
 }
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 82694ee..90bbca8 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -23,6 +23,8 @@
 
 import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_BIOMETRIC_MANAGER_CAN_AUTHENTICATE;
 
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -30,6 +32,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -86,6 +89,17 @@
             BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
 
     /**
+     * Returned from {@link BiometricManager#getLastAuthenticationTime(int)} when no matching
+     * successful authentication has been performed since boot.
+     */
+    @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME)
+    public static final long BIOMETRIC_NO_AUTHENTICATION =
+            BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+
+    private static final int GET_LAST_AUTH_TIME_ALLOWED_AUTHENTICATORS =
+            Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG;
+
+    /**
      * @hide
      */
     @IntDef({BIOMETRIC_SUCCESS,
@@ -637,5 +651,58 @@
         }
 
     }
+
+    /**
+     * Gets the last time the user successfully authenticated using one of the given authenticators.
+     * The returned value is time in
+     * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} (time since
+     * boot, including sleep).
+     * <p>
+     * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} is returned in the case where there
+     * has been no successful authentication using any of the given authenticators since boot.
+     * <p>
+     * Currently, only {@link Authenticators#DEVICE_CREDENTIAL} and
+     * {@link Authenticators#BIOMETRIC_STRONG} are accepted. {@link IllegalArgumentException} will
+     * be thrown if {@code authenticators} contains other authenticator types.
+     * <p>
+     * Note that this may return successful authentication times even if the device is currently
+     * locked. You may use {@link KeyguardManager#isDeviceLocked()} to determine if the device
+     * is unlocked or not. Additionally, this method may return valid times for an authentication
+     * method that is no longer available. For instance, if the user unlocked the device with a
+     * {@link Authenticators#BIOMETRIC_STRONG} authenticator but then deleted that authenticator
+     * (e.g., fingerprint data), this method will still return the time of that unlock for
+     * {@link Authenticators#BIOMETRIC_STRONG} if it is the most recent successful event. The caveat
+     * is that {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} will be returned if the device
+     * no longer has a secure lock screen at all, even if there were successful authentications
+     * performed before the lock screen was made insecure.
+     *
+     * @param authenticators bit field consisting of constants defined in {@link Authenticators}.
+     * @return the time of last authentication or
+     * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION}
+     * @throws IllegalArgumentException if {@code authenticators} contains values other than
+     * {@link Authenticators#DEVICE_CREDENTIAL} and {@link Authenticators#BIOMETRIC_STRONG} or is
+     * 0.
+     */
+    @RequiresPermission(USE_BIOMETRIC)
+    @ElapsedRealtimeLong
+    @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME)
+    public long getLastAuthenticationTime(
+            @BiometricManager.Authenticators.Types int authenticators) {
+        if (authenticators == 0
+                || (GET_LAST_AUTH_TIME_ALLOWED_AUTHENTICATORS & authenticators) != authenticators) {
+            throw new IllegalArgumentException(
+                    "Only BIOMETRIC_STRONG and DEVICE_CREDENTIAL authenticators may be used.");
+        }
+
+        if (mService != null) {
+            try {
+                return mService.getLastAuthenticationTime(UserHandle.myUserId(), authenticators);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            return BIOMETRIC_NO_AUTHENTICATION;
+        }
+    }
 }
 
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 294813d..35ae11c 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -786,7 +786,7 @@
          * Get {@link Signature} object.
          * @return {@link Signature} object or null if this doesn't contain one.
          */
-        public Signature getSignature() {
+        public @Nullable Signature getSignature() {
             return super.getSignature();
         }
 
@@ -794,7 +794,7 @@
          * Get {@link Cipher} object.
          * @return {@link Cipher} object or null if this doesn't contain one.
          */
-        public Cipher getCipher() {
+        public @Nullable Cipher getCipher() {
             return super.getCipher();
         }
 
@@ -802,7 +802,7 @@
          * Get {@link Mac} object.
          * @return {@link Mac} object or null if this doesn't contain one.
          */
-        public Mac getMac() {
+        public @Nullable Mac getMac() {
             return super.getMac();
         }
 
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 6ac1efb..39fbe83 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -20,6 +20,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.security.identity.IdentityCredential;
 import android.security.identity.PresentationSession;
 import android.security.keystore2.AndroidKeyStoreProvider;
@@ -33,20 +34,35 @@
 /**
  * A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager.
  * Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac},
- * {@link IdentityCredential}, and {@link PresentationSession} objects.
+ * {@link KeyAgreement}, {@link IdentityCredential}, and {@link PresentationSession} objects.
  * @hide
  */
 public class CryptoObject {
     private final Object mCrypto;
 
+    /**
+     * Create from a {@link Signature} object.
+     *
+     * @param signature a {@link Signature} object.
+     */
     public CryptoObject(@NonNull Signature signature) {
         mCrypto = signature;
     }
 
+    /**
+     * Create from a {@link Cipher} object.
+     *
+     * @param cipher a {@link Cipher} object.
+     */
     public CryptoObject(@NonNull Cipher cipher) {
         mCrypto = cipher;
     }
 
+    /**
+     * Create from a {@link Mac} object.
+     *
+     * @param mac a {@link Mac} object.
+     */
     public CryptoObject(@NonNull Mac mac) {
         mCrypto = mac;
     }
@@ -62,10 +78,20 @@
         mCrypto = credential;
     }
 
+    /**
+     * Create from a {@link PresentationSession} object.
+     *
+     * @param session a {@link PresentationSession} object.
+     */
     public CryptoObject(@NonNull PresentationSession session) {
         mCrypto = session;
     }
 
+    /**
+     * Create from a {@link KeyAgreement} object.
+     *
+     * @param keyAgreement a {@link KeyAgreement} object.
+     */
     @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
     public CryptoObject(@NonNull KeyAgreement keyAgreement) {
         mCrypto = keyAgreement;
@@ -75,7 +101,7 @@
      * Get {@link Signature} object.
      * @return {@link Signature} object or null if this doesn't contain one.
      */
-    public Signature getSignature() {
+    public @Nullable Signature getSignature() {
         return mCrypto instanceof Signature ? (Signature) mCrypto : null;
     }
 
@@ -83,7 +109,7 @@
      * Get {@link Cipher} object.
      * @return {@link Cipher} object or null if this doesn't contain one.
      */
-    public Cipher getCipher() {
+    public @Nullable Cipher getCipher() {
         return mCrypto instanceof Cipher ? (Cipher) mCrypto : null;
     }
 
@@ -91,7 +117,7 @@
      * Get {@link Mac} object.
      * @return {@link Mac} object or null if this doesn't contain one.
      */
-    public Mac getMac() {
+    public @Nullable Mac getMac() {
         return mCrypto instanceof Mac ? (Mac) mCrypto : null;
     }
 
@@ -101,7 +127,7 @@
      * @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
      */
     @Deprecated
-    public IdentityCredential getIdentityCredential() {
+    public @Nullable IdentityCredential getIdentityCredential() {
         return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null;
     }
 
@@ -109,16 +135,18 @@
      * Get {@link PresentationSession} object.
      * @return {@link PresentationSession} object or null if this doesn't contain one.
      */
-    public PresentationSession getPresentationSession() {
+    public @Nullable PresentationSession getPresentationSession() {
         return mCrypto instanceof PresentationSession ? (PresentationSession) mCrypto : null;
     }
 
     /**
-     * Get {@link KeyAgreement} object.
+     * Get {@link KeyAgreement} object. A key-agreement protocol is a protocol whereby
+     * two or more parties can agree on a shared secret using public key cryptography.
+     *
      * @return {@link KeyAgreement} object or null if this doesn't contain one.
      */
     @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
-    public KeyAgreement getKeyAgreement() {
+    public @Nullable KeyAgreement getKeyAgreement() {
         return mCrypto instanceof KeyAgreement ? (KeyAgreement) mCrypto : null;
     }
 
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index c2e5c0b..5bdbe2b5 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -57,6 +57,9 @@
     // Checks if biometrics can be used.
     int canAuthenticate(String opPackageName, int userId, int authenticators);
 
+    // Gets the time of last authentication for the given user and authenticators.
+    long getLastAuthenticationTime(int userId, int authenticators);
+
     // Checks if any biometrics are enrolled.
     boolean hasEnrolledBiometrics(int userId, String opPackageName);
 
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 18c8d1b..058f302 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -53,6 +53,10 @@
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     int canAuthenticate(String opPackageName, int userId, int callingUserId, int authenticators);
 
+    // Gets the time of last authentication for the given user and authenticators.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    long getLastAuthenticationTime(int userId, int authenticators);
+
     // Checks if any biometrics are enrolled.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     boolean hasEnrolledBiometrics(int userId, String opPackageName);
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 0924e0d1..c370ad6 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -1,6 +1,13 @@
 package: "android.hardware.biometrics"
 
 flag {
+    name: "last_authentication_time"
+    namespace: "wallet_integration"
+    description: "Feature flag for adding getLastAuthenticationTime API to BiometricManager"
+    bug: "301979982"
+}
+
+flag {
   name: "add_key_agreement_crypto_object"
   namespace: "biometrics"
   description: "Feature flag for adding KeyAgreement api to CryptoObject."
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 21540bf..d6c58ac 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1403,6 +1403,7 @@
                 if (mSurfaces.get(i) != other.mSurfaces.get(i))
                     return false;
             }
+            if (!mIsDeferredConfig && mSurfaces.size() != other.mSurfaces.size()) return false;
             if (mDynamicRangeProfile != other.mDynamicRangeProfile) {
                 return false;
             }
diff --git a/core/java/android/hardware/hdmi/OWNERS b/core/java/android/hardware/hdmi/OWNERS
index 861e440..6952e5d 100644
--- a/core/java/android/hardware/hdmi/OWNERS
+++ b/core/java/android/hardware/hdmi/OWNERS
@@ -2,5 +2,4 @@
 
 include /services/core/java/com/android/server/display/OWNERS
 
-marvinramin@google.com
-lcnathalie@google.com
+quxiangfang@google.com
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 01ce7b9..481ec72 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -15,6 +15,8 @@
  */
 package android.hardware.location;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -1111,12 +1113,12 @@
         }
     };
 
-    /** @throws ServiceNotFoundException
-     * @hide */
-    public ContextHubManager(Context context, Looper mainLooper) throws ServiceNotFoundException {
+    /** @hide */
+    public ContextHubManager(@NonNull IContextHubService service, @NonNull Looper mainLooper) {
+        requireNonNull(service, "service cannot be null");
+        requireNonNull(mainLooper, "mainLooper cannot be null");
+        mService = service;
         mMainLooper = mainLooper;
-        mService = IContextHubService.Stub.asInterface(
-                ServiceManager.getServiceOrThrow(Context.CONTEXTHUB_SERVICE));
         try {
             mService.registerCallback(mClientCallback);
         } catch (RemoteException e) {
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index dbb3127..19ba6a1 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -23,6 +23,7 @@
 import android.os.IBinder;
 import android.os.ServiceManager;
 
+import com.android.net.flags.Flags;
 import com.android.net.module.util.PermissionUtils;
 /**
  * Constants and utilities for client code communicating with the network stack service.
@@ -103,4 +104,16 @@
             final @NonNull String... otherPermissions) {
         PermissionUtils.enforceNetworkStackPermissionOr(context, otherPermissions);
     }
+
+    /**
+     * Get setting of the "set_data_saver_via_cm" flag.
+     *
+     * @hide
+     */
+    // A workaround for aconfig. Currently, aconfig value read from platform and mainline code can
+    // be inconsistent. To avoid the problem, CTS for mainline code can get the flag value by this
+    // method.
+    public static boolean getDataSaverViaCmFlag() {
+        return Flags.setDataSaverViaCm();
+    }
 }
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index a40fb15..6f11d3a 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -16,10 +16,12 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.Flags.FLAG_SAFE_MODE_CONFIG;
 import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -235,6 +237,9 @@
             "mMinUdpPort4500NatTimeoutSeconds";
     private final int mMinUdpPort4500NatTimeoutSeconds;
 
+    private static final String IS_SAFE_MODE_DISABLED_KEY = "mIsSafeModeDisabled";
+    private final boolean mIsSafeModeDisabled;
+
     private static final String GATEWAY_OPTIONS_KEY = "mGatewayOptions";
     @NonNull private final Set<Integer> mGatewayOptions;
 
@@ -247,6 +252,7 @@
             @NonNull long[] retryIntervalsMs,
             @IntRange(from = MIN_MTU_V6) int maxMtu,
             @NonNull int minUdpPort4500NatTimeoutSeconds,
+            boolean isSafeModeDisabled,
             @NonNull Set<Integer> gatewayOptions) {
         mGatewayConnectionName = gatewayConnectionName;
         mTunnelConnectionParams = tunnelConnectionParams;
@@ -255,6 +261,7 @@
         mMaxMtu = maxMtu;
         mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
         mGatewayOptions = Collections.unmodifiableSet(new ArraySet(gatewayOptions));
+        mIsSafeModeDisabled = isSafeModeDisabled;
 
         mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates);
         if (mUnderlyingNetworkTemplates.isEmpty()) {
@@ -317,6 +324,7 @@
                 in.getInt(
                         MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY,
                         MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET);
+        mIsSafeModeDisabled = in.getBoolean(IS_SAFE_MODE_DISABLED_KEY);
 
         validate();
     }
@@ -483,6 +491,16 @@
     }
 
     /**
+     * Check whether safe mode is enabled
+     *
+     * @see Builder#setSafeModeEnabled(boolean)
+     */
+    @FlaggedApi(FLAG_SAFE_MODE_CONFIG)
+    public boolean isSafeModeEnabled() {
+        return !mIsSafeModeDisabled;
+    }
+
+    /**
      * Checks if the given VCN gateway option is enabled.
      *
      * @param option the option to check.
@@ -528,6 +546,7 @@
         result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
         result.putInt(MAX_MTU_KEY, mMaxMtu);
         result.putInt(MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS_KEY, mMinUdpPort4500NatTimeoutSeconds);
+        result.putBoolean(IS_SAFE_MODE_DISABLED_KEY, mIsSafeModeDisabled);
 
         return result;
     }
@@ -542,6 +561,7 @@
                 Arrays.hashCode(mRetryIntervalsMs),
                 mMaxMtu,
                 mMinUdpPort4500NatTimeoutSeconds,
+                mIsSafeModeDisabled,
                 mGatewayOptions);
     }
 
@@ -559,6 +579,7 @@
                 && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
                 && mMaxMtu == rhs.mMaxMtu
                 && mMinUdpPort4500NatTimeoutSeconds == rhs.mMinUdpPort4500NatTimeoutSeconds
+                && mIsSafeModeDisabled == rhs.mIsSafeModeDisabled
                 && mGatewayOptions.equals(rhs.mGatewayOptions);
     }
 
@@ -577,6 +598,7 @@
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
         private int mMaxMtu = DEFAULT_MAX_MTU;
         private int mMinUdpPort4500NatTimeoutSeconds = MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET;
+        private boolean mIsSafeModeDisabled = false;
 
         @NonNull private final Set<Integer> mGatewayOptions = new ArraySet<>();
 
@@ -789,6 +811,27 @@
         }
 
         /**
+         * Enable/disable safe mode
+         *
+         * <p>If a VCN fails to provide connectivity within a system-provided timeout, it will enter
+         * safe mode. In safe mode, the VCN Network will be torn down and the system will restore
+         * connectivity by allowing underlying cellular or WiFi networks to be used as default. At
+         * the same time, VCN will continue to retry until it succeeds.
+         *
+         * <p>When safe mode is disabled and VCN connection fails to provide connectivity, end users
+         * might not have connectivity, and may not have access to carrier-owned underlying
+         * networks.
+         *
+         * @param enabled whether safe mode should be enabled. Defaults to {@code true}
+         */
+        @FlaggedApi(FLAG_SAFE_MODE_CONFIG)
+        @NonNull
+        public Builder setSafeModeEnabled(boolean enabled) {
+            mIsSafeModeDisabled = !enabled;
+            return this;
+        }
+
+        /**
          * Builds and validates the VcnGatewayConnectionConfig.
          *
          * @return an immutable VcnGatewayConnectionConfig instance
@@ -803,6 +846,7 @@
                     mRetryIntervalsMs,
                     mMaxMtu,
                     mMinUdpPort4500NatTimeoutSeconds,
+                    mIsSafeModeDisabled,
                     mGatewayOptions);
         }
     }
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
new file mode 100644
index 0000000..6956916
--- /dev/null
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.net.vcn"
+
+flag {
+    name: "safe_mode_config"
+    namespace: "vcn"
+    description: "Feature flag for safe mode configurability"
+    bug: "276358140"
+}
\ No newline at end of file
diff --git a/core/java/android/nfc/Constants.java b/core/java/android/nfc/Constants.java
new file mode 100644
index 0000000..f768330
--- /dev/null
+++ b/core/java/android/nfc/Constants.java
@@ -0,0 +1,29 @@
+/*
+ * 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 android.nfc;
+
+/**
+ * @hide
+ * TODO(b/303286040): Holds @hide API constants. Formalize these APIs.
+ */
+public final class Constants {
+    private Constants() { }
+
+    public static final String SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND = "nfc_payment_foreground";
+    public static final String SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
+    public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any";
+}
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
index 53843fe..c7b3b2c 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -40,5 +40,6 @@
     boolean unsetPreferredService();
     boolean supportsAidPrefixRegistration();
     ApduServiceInfo getPreferredPaymentService(int userHandle);
+    boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
     boolean isDefaultPaymentRegistered();
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 4658630..c897595 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -24,6 +24,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -37,6 +38,7 @@
 import android.nfc.tech.Ndef;
 import android.nfc.tech.NfcA;
 import android.nfc.tech.NfcF;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -376,6 +378,8 @@
      * <p>An external NFC field detected when device locked and SecureNfc enabled.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC =
             "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
 
@@ -942,7 +946,8 @@
      *
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     public int getAdapterState() {
         try {
             return sService.getState();
@@ -1594,6 +1599,40 @@
         mNfcActivityManager.disableReaderMode(activity);
     }
 
+    // Flags arguments to NFC adapter to enable/disable NFC
+    private static final int DISABLE_POLLING_FLAGS = 0x1000;
+    private static final int ENABLE_POLLING_FLAGS = 0x0000;
+
+    /**
+     * Privileged API to enable disable reader polling.
+     * Note: Use with caution! The app is responsible for ensuring that the polling state is
+     * returned to normal.
+     *
+     * @see #enableReaderMode(Activity, ReaderCallback, int, Bundle)  for more detailed
+     * documentation.
+     *
+     * @param enablePolling whether to enable or disable polling.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @SuppressLint("VisiblySynchronized")
+    public void setReaderMode(boolean enablePolling) {
+        synchronized (NfcAdapter.class) {
+            if (!sHasNfcFeature) {
+                throw new UnsupportedOperationException();
+            }
+        }
+        Binder token = new Binder();
+        int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
+        try {
+            NfcAdapter.sService.setReaderMode(token, null, flags, null);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+        }
+    }
+
     /**
      * Manually invoke Android Beam to share data.
      *
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
index 958669e..ae3e333 100644
--- a/core/java/android/nfc/cardemulation/AidGroup.java
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -34,6 +34,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.regex.Pattern;
 
 /**********************************************************************
  * This file is not a part of the NFC mainline module                 *
@@ -79,7 +80,7 @@
             throw new IllegalArgumentException("Too many AIDs in AID group.");
         }
         for (String aid : aids) {
-            if (!CardEmulation.isValidAid(aid)) {
+            if (!isValidAid(aid)) {
                 throw new IllegalArgumentException("AID " + aid + " is not a valid AID.");
             }
         }
@@ -264,4 +265,34 @@
         return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
                 CardEmulation.CATEGORY_OTHER.equals(category);
     }
+
+    private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+    /**
+     * Copied over from {@link CardEmulation#isValidAid(String)}
+     * @hide
+     */
+    private static boolean isValidAid(String aid) {
+        if (aid == null)
+            return false;
+
+        // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+        if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+        if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // Verify hex characters
+        if (!AID_PATTERN.matcher(aid).matches()) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 18ec914..597c948 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,6 +52,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Pattern;
 
 /**
  * Class holding APDU service info.
@@ -126,6 +127,11 @@
     private final String mSettingsActivityName;
 
     /**
+     * State of the service for CATEGORY_OTHER selection
+     */
+    private boolean mOtherServiceEnabled;
+
+    /**
      * @hide
      */
     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
@@ -133,8 +139,21 @@
             boolean requiresUnlock, int bannerResource, int uid,
             String settingsActivityName, String offHost, String staticOffHost) {
         this(info, onHost, description, staticAidGroups, dynamicAidGroups,
+                requiresUnlock, bannerResource, uid, settingsActivityName,
+                offHost, staticOffHost, false);
+    }
+
+    /**
+     * @hide
+     */
+    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+            List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
+            boolean requiresUnlock, int bannerResource, int uid,
+            String settingsActivityName, String offHost, String staticOffHost,
+            boolean isEnabled) {
+        this(info, onHost, description, staticAidGroups, dynamicAidGroups,
                 requiresUnlock, onHost ? true : false, bannerResource, uid,
-                settingsActivityName, offHost, staticOffHost);
+                settingsActivityName, offHost, staticOffHost, isEnabled);
     }
 
     /**
@@ -143,7 +162,7 @@
     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
             List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
             boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
-            String settingsActivityName, String offHost, String staticOffHost) {
+            String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) {
         this.mService = info;
         this.mDescription = description;
         this.mStaticAidGroups = new HashMap<String, AidGroup>();
@@ -162,6 +181,8 @@
         this.mBannerResourceId = bannerResource;
         this.mUid = uid;
         this.mSettingsActivityName = settingsActivityName;
+        this.mOtherServiceEnabled = isEnabled;
+
     }
 
     /**
@@ -307,7 +328,7 @@
                             com.android.internal.R.styleable.AidFilter);
                     String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
                             toUpperCase();
-                    if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
                         currentGroup.getAids().add(aid);
                     } else {
                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -321,7 +342,7 @@
                             toUpperCase();
                     // Add wildcard char to indicate prefix
                     aid = aid.concat("*");
-                    if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
                         currentGroup.getAids().add(aid);
                     } else {
                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -335,7 +356,7 @@
                             toUpperCase();
                     // Add wildcard char to indicate suffix
                     aid = aid.concat("#");
-                    if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
                         currentGroup.getAids().add(aid);
                     } else {
                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -350,6 +371,9 @@
         }
         // Set uid
         mUid = si.applicationInfo.uid;
+
+        mOtherServiceEnabled = false;    // support other category
+
     }
 
     /**
@@ -719,43 +743,47 @@
         dest.writeInt(mBannerResourceId);
         dest.writeInt(mUid);
         dest.writeString(mSettingsActivityName);
+
+        dest.writeInt(mOtherServiceEnabled ? 1 : 0);
     };
 
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     public static final @NonNull Parcelable.Creator<ApduServiceInfo> CREATOR =
             new Parcelable.Creator<ApduServiceInfo>() {
-        @Override
-        public ApduServiceInfo createFromParcel(Parcel source) {
-            ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
-            String description = source.readString();
-            boolean onHost = source.readInt() != 0;
-            String offHostName = source.readString();
-            String staticOffHostName = source.readString();
-            ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
-            int numStaticGroups = source.readInt();
-            if (numStaticGroups > 0) {
-                source.readTypedList(staticAidGroups, AidGroup.CREATOR);
-            }
-            ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
-            int numDynamicGroups = source.readInt();
-            if (numDynamicGroups > 0) {
-                source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
-            }
-            boolean requiresUnlock = source.readInt() != 0;
-            boolean requiresScreenOn = source.readInt() != 0;
-            int bannerResource = source.readInt();
-            int uid = source.readInt();
-            String settingsActivityName = source.readString();
-            return new ApduServiceInfo(info, onHost, description, staticAidGroups,
-                    dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
-                    settingsActivityName, offHostName, staticOffHostName);
-        }
+                @Override
+                public ApduServiceInfo createFromParcel(Parcel source) {
+                    ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
+                    String description = source.readString();
+                    boolean onHost = source.readInt() != 0;
+                    String offHostName = source.readString();
+                    String staticOffHostName = source.readString();
+                    ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
+                    int numStaticGroups = source.readInt();
+                    if (numStaticGroups > 0) {
+                        source.readTypedList(staticAidGroups, AidGroup.CREATOR);
+                    }
+                    ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
+                    int numDynamicGroups = source.readInt();
+                    if (numDynamicGroups > 0) {
+                        source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
+                    }
+                    boolean requiresUnlock = source.readInt() != 0;
+                    boolean requiresScreenOn = source.readInt() != 0;
+                    int bannerResource = source.readInt();
+                    int uid = source.readInt();
+                    String settingsActivityName = source.readString();
+                    boolean isEnabled = source.readInt() != 0;
+                    return new ApduServiceInfo(info, onHost, description, staticAidGroups,
+                            dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
+                            settingsActivityName, offHostName, staticOffHostName,
+                            isEnabled);
+                }
 
-        @Override
-        public ApduServiceInfo[] newArray(int size) {
-            return new ApduServiceInfo[size];
-        }
-    };
+                @Override
+                public ApduServiceInfo[] newArray(int size) {
+                    return new ApduServiceInfo[size];
+                }
+            };
 
     /**
      * Dump contents for debugging.
@@ -778,14 +806,16 @@
         }
         pw.println("    Static AID groups:");
         for (AidGroup group : mStaticAidGroups.values()) {
-            pw.println("        Category: " + group.getCategory());
+            pw.println("        Category: " + group.getCategory()
+                    + "(enabled: " + mOtherServiceEnabled + ")");
             for (String aid : group.getAids()) {
                 pw.println("            AID: " + aid);
             }
         }
         pw.println("    Dynamic AID groups:");
         for (AidGroup group : mDynamicAidGroups.values()) {
-            pw.println("        Category: " + group.getCategory());
+            pw.println("        Category: " + group.getCategory()
+                    + "(enabled: " + mOtherServiceEnabled + ")");
             for (String aid : group.getAids()) {
                 pw.println("            AID: " + aid);
             }
@@ -795,6 +825,28 @@
         pw.println("    Requires Device ScreenOn: " + mRequiresDeviceScreenOn);
     }
 
+
+    /**
+     * Enable or disable this CATEGORY_OTHER service.
+     *
+     * @param enabled true to indicate if user has enabled this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void setOtherServiceEnabled(boolean enabled) {
+        mOtherServiceEnabled = enabled;
+    }
+
+
+    /**
+     * Returns whether this CATEGORY_OTHER service is enabled or not.
+     *
+     * @return true to indicate if user has enabled this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean isOtherServiceEnabled() {
+        return mOtherServiceEnabled;
+    }
+
     /**
      * Dump debugging info as ApduServiceInfoProto.
      *
@@ -806,7 +858,7 @@
      */
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     public void dumpDebug(@NonNull ProtoOutputStream proto) {
-        Utils.dumpDebugComponentName(getComponent(), proto, ApduServiceInfoProto.COMPONENT_NAME);
+        getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME);
         proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
         proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
         if (!mOnHost) {
@@ -825,4 +877,34 @@
         }
         proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName);
     }
+
+    private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+    /**
+     * Copied over from {@link CardEmulation#isValidAid(String)}
+     * @hide
+     */
+    private static boolean isValidAid(String aid) {
+        if (aid == null)
+            return false;
+
+        // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+        if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+        if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // Verify hex characters
+        if (!AID_PATTERN.matcher(aid).matches()) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        return true;
+    }
 }
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 4909b08..d048b59 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -16,16 +16,21 @@
 
 package android.nfc.cardemulation;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.nfc.Constants;
+import android.nfc.Flags;
 import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
 import android.os.RemoteException;
@@ -274,7 +279,7 @@
             try {
                 preferForeground = Settings.Secure.getInt(
                         contextAsUser.getContentResolver(),
-                        Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0;
+                        Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0;
             } catch (SettingNotFoundException e) {
             }
             return preferForeground;
@@ -876,9 +881,16 @@
     }
 
     /**
+     * Retrieves list of services registered of the provided category for the provided user.
+     *
+     * @param category Category string, one of {@link #CATEGORY_PAYMENT} or {@link #CATEGORY_OTHER}
+     * @param userId the user handle of the user whose information is being requested.
      * @hide
      */
-    public List<ApduServiceInfo> getServices(String category, int userId) {
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<ApduServiceInfo> getServices(@NonNull String category, @UserIdInt int userId) {
         try {
             return sService.getServices(userId, category);
         } catch (RemoteException e) {
@@ -935,6 +947,39 @@
         return true;
     }
 
+    /**
+     * Allows to set or unset preferred service (category other) to avoid  AID Collision.
+     *
+     * @param service The ComponentName of the service
+     * @param status  true to enable, false to disable
+     * @return set service for the category and true if service is already set return false.
+     *
+     * @hide
+     */
+    public boolean setServiceEnabledForCategoryOther(ComponentName service, boolean status) {
+        if (service == null) {
+            throw new NullPointerException("activity or service or category is null");
+        }
+        int userId = mContext.getUser().getIdentifier();
+
+        try {
+            return sService.setServiceEnabledForCategoryOther(userId, service, status);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setServiceEnabledForCategoryOther(userId, service, status);
+            } 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/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
index ec919e4..33bc169 100644
--- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -173,7 +173,7 @@
                             com.android.internal.R.styleable.SystemCodeFilter);
                     systemCode = a.getString(
                             com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase();
-                    if (!NfcFCardEmulation.isValidSystemCode(systemCode) &&
+                    if (!isValidSystemCode(systemCode) &&
                             !systemCode.equalsIgnoreCase("NULL")) {
                         Log.e(TAG, "Invalid System Code: " + systemCode);
                         systemCode = null;
@@ -187,7 +187,7 @@
                             com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase();
                     if (!nfcid2.equalsIgnoreCase("RANDOM") &&
                             !nfcid2.equalsIgnoreCase("NULL") &&
-                            !NfcFCardEmulation.isValidNfcid2(nfcid2)) {
+                            !isValidNfcid2(nfcid2)) {
                         Log.e(TAG, "Invalid NFCID2: " + nfcid2);
                         nfcid2 = null;
                     }
@@ -436,10 +436,62 @@
      */
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     public void dumpDebug(@NonNull ProtoOutputStream proto) {
-        Utils.dumpDebugComponentName(getComponent(), proto, NfcFServiceInfoProto.COMPONENT_NAME);
+        getComponent().dumpDebug(proto, NfcFServiceInfoProto.COMPONENT_NAME);
         proto.write(NfcFServiceInfoProto.DESCRIPTION, getDescription());
         proto.write(NfcFServiceInfoProto.SYSTEM_CODE, getSystemCode());
         proto.write(NfcFServiceInfoProto.NFCID2, getNfcid2());
         proto.write(NfcFServiceInfoProto.T3T_PMM, getT3tPmm());
     }
+
+    /**
+     * Copied over from {@link NfcFCardEmulation#isValidSystemCode(String)}
+     * @hide
+     */
+    private static boolean isValidSystemCode(String systemCode) {
+        if (systemCode == null) {
+            return false;
+        }
+        if (systemCode.length() != 4) {
+            Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+            return false;
+        }
+        // check if the value is between "4000" and "4FFF" (excluding "4*FF")
+        if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) {
+            Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+            return false;
+        }
+        try {
+            Integer.parseInt(systemCode, 16);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Copied over from {@link NfcFCardEmulation#isValidNfcid2(String)}
+     * @hide
+     */
+    private static boolean isValidNfcid2(String nfcid2) {
+        if (nfcid2 == null) {
+            return false;
+        }
+        if (nfcid2.length() != 16) {
+            Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+            return false;
+        }
+        // check if the the value starts with "02FE"
+        if (!nfcid2.toUpperCase().startsWith("02FE")) {
+            Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+            return false;
+        }
+        try {
+            Long.parseLong(nfcid2, 16);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 55b0b42..cd50ace 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -13,3 +13,10 @@
     description: "Flag for NFC reader option API changes"
     bug: "291187960"
 }
+
+flag {
+    name: "enable_nfc_user_restriction"
+    namespace: "nfc"
+    description: "Flag for NFC user restriction"
+    bug: "291187960"
+}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 6a4ec9b..25fba60 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -21,6 +21,7 @@
 import android.Manifest.permission;
 import android.annotation.FlaggedApi;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -236,6 +237,7 @@
                                             OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
 
     /** @hide */
+    @SuppressLint("UnflaggedApi") // TestApi without associated feature.
     @TestApi
     public static final int BATTERY_PLUGGED_ANY =
             BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 509c3b8..a9b7257 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -1227,6 +1228,7 @@
         /**
          * Vanilla Ice Cream.
          */
+        @FlaggedApi(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM)
         public static final int VANILLA_ICE_CREAM = CUR_DEVELOPMENT;
     }
 
diff --git a/core/java/android/os/DeadObjectException.java b/core/java/android/os/DeadObjectException.java
index e06b0f9..65ed618 100644
--- a/core/java/android/os/DeadObjectException.java
+++ b/core/java/android/os/DeadObjectException.java
@@ -19,7 +19,8 @@
 
 /**
  * The object you are calling has died, because its hosting process
- * no longer exists.
+ * no longer exists. This is also thrown for low-level binder
+ * errors.
  */
 public class DeadObjectException extends RemoteException {
     public DeadObjectException() {
diff --git a/core/java/android/os/DeadSystemRuntimeException.java b/core/java/android/os/DeadSystemRuntimeException.java
index 1e86924..82b1ad8 100644
--- a/core/java/android/os/DeadSystemRuntimeException.java
+++ b/core/java/android/os/DeadSystemRuntimeException.java
@@ -18,9 +18,10 @@
 
 /**
  * Exception thrown when a call into system_server resulted in a
- * DeadObjectException, meaning that the system_server has died.  There's
- * nothing apps can do at this point - the system will automatically restart -
- * so there's no point in catching this.
+ * DeadObjectException, meaning that the system_server has died or
+ * experienced a low-level binder error.  There's * nothing apps can
+ * do at this point - the system will automatically restart - so
+ * there's no point in catching this.
  *
  * @hide
  */
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 92be4c0..8fcff78 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -115,6 +115,7 @@
     private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
     private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
     private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
+    private static final String SYSTEM_ANGLE_STRING = "system";
 
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
@@ -122,7 +123,7 @@
     private GameManager mGameManager;
 
     private int mAngleOptInIndex = -1;
-    private boolean mEnabledByGameMode = false;
+    private boolean mShouldUseAngle = false;
 
     /**
      * Set up GraphicsEnvironment
@@ -141,19 +142,16 @@
 
         // Setup ANGLE and pass down ANGLE details to the C++ code
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
-        boolean useAngle = false;
         if (setupAngle(context, coreSettings, pm, packageName)) {
-            if (shouldUseAngle(context, coreSettings, packageName)) {
-                useAngle = true;
-                setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
-                        0, packageName, getVulkanVersion(pm));
-            }
+            mShouldUseAngle = true;
+            setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
+                    0, packageName, getVulkanVersion(pm));
         }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
         if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
-            if (!useAngle) {
+            if (!mShouldUseAngle) {
                 setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME,
                         SYSTEM_DRIVER_VERSION_CODE,
                         SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0),
@@ -178,32 +176,16 @@
     }
 
     /**
-     * Query to determine if the Game Mode has enabled ANGLE.
+     * Query to determine the ANGLE driver choice.
      */
-    private boolean isAngleEnabledByGameMode(Context context, String packageName) {
-        try {
-            final boolean gameModeEnabledAngle =
-                    (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
-            Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
-            return gameModeEnabledAngle;
-        } catch (SecurityException e) {
-            Log.e(TAG, "Caught exception while querying GameManagerService if ANGLE is enabled "
-                    + "for package: " + packageName);
-        }
-
-        return false;
-    }
-
-    /**
-     * Query to determine if ANGLE should be used
-     */
-    private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
+    private String queryAngleChoice(Context context, Bundle coreSettings,
+                                               String packageName) {
         if (TextUtils.isEmpty(packageName)) {
             Log.v(TAG, "No package name specified; use the system driver");
-            return false;
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
-        return shouldUseAngleInternal(context, coreSettings, packageName);
+        return queryAngleChoiceInternal(context, coreSettings, packageName);
     }
 
     private int getVulkanVersion(PackageManager pm) {
@@ -421,13 +403,13 @@
      * 2) The per-application switch (i.e. Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS and
      *    Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES; which corresponds to the
      *    “angle_gl_driver_selection_pkgs” and “angle_gl_driver_selection_values” settings); if it
-     *    forces a choice;
-     * 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
+     *    forces a choice.
      */
-    private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
+    private String queryAngleChoiceInternal(Context context, Bundle bundle,
+                                                       String packageName) {
         // Make sure we have a good package name
         if (TextUtils.isEmpty(packageName)) {
-            return false;
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
         // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
@@ -442,7 +424,7 @@
         }
         if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
             Log.v(TAG, "Turn on ANGLE for all applications.");
-            return true;
+            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
         }
 
         // Get the per-application settings lists
@@ -455,17 +437,15 @@
         Log.v(TAG, "  angle_gl_driver_selection_pkgs=" + optInPackages);
         Log.v(TAG, "  angle_gl_driver_selection_values=" + optInValues);
 
-        mEnabledByGameMode = isAngleEnabledByGameMode(context, packageName);
-
         // Make sure we have good settings to use
-        if (optInPackages.size() != optInValues.size()) {
+        if (optInPackages.size() == 0 || optInPackages.size() != optInValues.size()) {
             Log.v(TAG,
                     "Global.Settings values are invalid: "
                         + "number of packages: "
                             + optInPackages.size() + ", "
                         + "number of values: "
                             + optInValues.size());
-            return mEnabledByGameMode;
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
         // See if this application is listed in the per-application settings list
@@ -473,7 +453,7 @@
 
         if (pkgIndex < 0) {
             Log.v(TAG, packageName + " is not listed in per-application setting");
-            return mEnabledByGameMode;
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
         mAngleOptInIndex = pkgIndex;
 
@@ -484,14 +464,12 @@
                 "ANGLE Developer option for '" + packageName + "' "
                         + "set to: '" + optInValue + "'");
         if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
-            return true;
+            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
         } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
-            return false;
-        } else {
-            // The user either chose default or an invalid value; go with the default driver or what
-            // the game mode indicates
-            return mEnabledByGameMode;
+            return ANGLE_GL_DRIVER_CHOICE_NATIVE;
         }
+        // The user either chose default or an invalid value; go with the default driver.
+        return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
     }
 
     /**
@@ -503,10 +481,12 @@
         final List<ResolveInfo> resolveInfos =
                 pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
         if (resolveInfos.size() != 1) {
-            Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
+            Log.v(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
                     + resolveInfos.size());
-            for (ResolveInfo resolveInfo : resolveInfos) {
-                Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
+            if (DEBUG) {
+                for (ResolveInfo resolveInfo : resolveInfos) {
+                    Log.d(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
+                }
             }
             return "";
         }
@@ -541,26 +521,46 @@
     }
 
     /**
-     * Determine whether ANGLE should be used, set it up if so, and pass ANGLE details down to
-     * the C++ GraphicsEnv class.
+     * Determine whether ANGLE should be used, attempt to set up from apk first, if ANGLE can be
+     * set up from apk, pass ANGLE details down to the C++ GraphicsEnv class via
+     * GraphicsEnv::setAngleInfo(). If apk setup fails, attempt to set up to use system ANGLE.
+     * Return false if both fail.
      *
-     * If ANGLE will be used, GraphicsEnv::setAngleInfo() will be called to enable ANGLE to be
-     * properly used.
-     *
-     * @param context
-     * @param bundle
-     * @param pm
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageManager - PackageManager of the application process.
      * @param packageName - package name of the application.
-     * @return true: ANGLE setup successfully
-     *         false: ANGLE not setup (not on allowlist, ANGLE not present, etc.)
+     * @return true: can set up to use ANGLE successfully.
+     *         false: can not set up to use ANGLE (not on allowlist, ANGLE not present, etc.)
      */
-    private boolean setupAngle(Context context, Bundle bundle, PackageManager pm,
+    private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
             String packageName) {
-
-        if (!shouldUseAngle(context, bundle, packageName)) {
+        final String angleChoice = queryAngleChoice(context, bundle, packageName);
+        if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_DEFAULT)) {
+            return false;
+        }
+        if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
+            nativeSetAngleInfo("", true, packageName, null);
             return false;
         }
 
+        return setupAngleFromApk(context, bundle, packageManager, packageName)
+                || setupAngleFromSystem(context, bundle, packageName);
+    }
+
+    /**
+     * Attempt to set up ANGLE from the packaged apk, if the apk can be found, pass ANGLE details to
+     * the C++ GraphicsEnv class.
+     *
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageManager - PackageManager of the application process.
+     * @param packageName - package name of the application.
+     * @return true: can set up to use ANGLE apk.
+     *         false: can not set up to use ANGLE apk (ANGLE apk not present, etc.)
+     */
+    private boolean setupAngleFromApk(Context context, Bundle bundle, PackageManager packageManager,
+            String packageName) {
         ApplicationInfo angleInfo = null;
 
         // If the developer has specified a debug package over ADB, attempt to find it
@@ -569,7 +569,7 @@
             Log.v(TAG, "ANGLE debug package enabled: " + anglePkgName);
             try {
                 // Note the debug package does not have to be pre-installed
-                angleInfo = pm.getApplicationInfo(anglePkgName, 0);
+                angleInfo = packageManager.getApplicationInfo(anglePkgName, 0);
             } catch (PackageManager.NameNotFoundException e) {
                 // If the debug package is specified but not found, abort.
                 Log.v(TAG, "ANGLE debug package '" + anglePkgName + "' not installed");
@@ -579,7 +579,7 @@
 
         // Otherwise, check to see if ANGLE is properly installed
         if (angleInfo == null) {
-            anglePkgName = getAnglePackageName(pm);
+            anglePkgName = getAnglePackageName(packageManager);
             if (TextUtils.isEmpty(anglePkgName)) {
                 Log.v(TAG, "Failed to find ANGLE package.");
                 return false;
@@ -588,7 +588,7 @@
             Log.v(TAG, "ANGLE package enabled: " + anglePkgName);
             try {
                 // Production ANGLE libraries must be pre-installed as a system app
-                angleInfo = pm.getApplicationInfo(anglePkgName,
+                angleInfo = packageManager.getApplicationInfo(anglePkgName,
                         PackageManager.MATCH_SYSTEM_ONLY);
             } catch (PackageManager.NameNotFoundException e) {
                 Log.v(TAG, "ANGLE package '" + anglePkgName + "' not installed");
@@ -609,15 +609,32 @@
             Log.d(TAG, "ANGLE package libs: " + paths);
         }
 
-        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
-        // and features to use.
+        // If we make it to here, ANGLE apk will be used.  Call nativeSetAngleInfo() with the
+        // application package name and ANGLE features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        setAngleInfo(paths, packageName, ANGLE_GL_DRIVER_CHOICE_ANGLE, features);
+        nativeSetAngleInfo(paths, false, packageName, features);
 
         return true;
     }
 
     /**
+     * Set up ANGLE from system.
+     *
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageName - package name of the application.
+     * @return true: can set up to use system ANGLE.
+     *         false: can not set up to use system ANGLE because it doesn't exist.
+     */
+    private boolean setupAngleFromSystem(Context context, Bundle bundle, String packageName) {
+        // System ANGLE always exists, call nativeSetAngleInfo() with the application package
+        // name and ANGLE features to use.
+        final String[] features = getAngleEglFeatures(context, bundle);
+        nativeSetAngleInfo(SYSTEM_ANGLE_STRING, false, packageName, features);
+        return true;
+    }
+
+    /**
      * Determine if the "ANGLE In Use" dialog box should be shown.
      */
     private boolean shouldShowAngleInUseDialogBox(Context context) {
@@ -636,7 +653,10 @@
     }
 
     /**
-     * Show the ANGLE in Use Dialog Box
+     * Show the ANGLE in use dialog box.
+     * The ANGLE in use dialog box will show up as long as the application
+     * should use ANGLE. It does not mean the application has successfully
+     * loaded ANGLE because this check happens before the loading completes.
      * @param context
      */
     public void showAngleInUseDialogBox(Context context) {
@@ -644,14 +664,15 @@
             return;
         }
 
-        final String packageName = context.getPackageName();
-        if (!getShouldUseAngle(packageName)) {
+        if (!mShouldUseAngle) {
             return;
         }
 
         final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE);
         final String anglePkg = getAnglePackageName(context.getPackageManager());
-        intent.setPackage(anglePkg);
+        if (anglePkg.isEmpty()) {
+            return;
+        }
 
         context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
             @Override
@@ -890,9 +911,8 @@
     private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
     private static native void setGpuStats(String driverPackageName, String driverVersionName,
             long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
-    private static native void setAngleInfo(String path, String appPackage,
-            String devOptIn, String[] features);
-    private static native boolean getShouldUseAngle(String packageName);
+    private static native void nativeSetAngleInfo(String path, boolean useNativeDriver,
+            String packageName, String[] features);
     private static native boolean setInjectLayersPrSetDumpable();
     private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
 
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 1a3dcee..a49ee7d 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -104,16 +104,6 @@
     void setIPv6AddrGenMode(String iface, int mode);
 
     /**
-     * Add the specified route to the interface.
-     */
-    void addRoute(int netId, in RouteInfo route);
-
-    /**
-     * Remove the specified route from the interface.
-     */
-    void removeRoute(int netId, in RouteInfo route);
-
-    /**
      * Shuts down the service
      */
     @EnforcePermission("SHUTDOWN")
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 180735b..d12e3b2 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -2023,9 +2023,13 @@
             return;
         }
 
+        // Temporarily disable checks so that explicit GC is allowed.
+        final int oldMask = getThreadPolicyMask();
+        setThreadPolicyMask(0);
         System.gc();
         System.runFinalization();
         System.gc();
+        setThreadPolicyMask(oldMask);
 
         // Note: classInstanceLimit is immutable, so this is lock-free
         // Create the classes array.
@@ -2372,14 +2376,14 @@
     }
 
     /** Assume locked until we hear otherwise */
-    private static volatile boolean sUserKeyUnlocked = false;
+    private static volatile boolean sCeStorageUnlocked = false;
 
-    private static boolean isUserKeyUnlocked(int userId) {
+    private static boolean isCeStorageUnlocked(int userId) {
         final IStorageManager storage = IStorageManager.Stub
                 .asInterface(ServiceManager.getService("mount"));
         if (storage != null) {
             try {
-                return storage.isUserKeyUnlocked(userId);
+                return storage.isCeStorageUnlocked(userId);
             } catch (RemoteException ignored) {
             }
         }
@@ -2392,13 +2396,13 @@
         // since any relocking of that user will always result in our
         // process being killed to release any CE FDs we're holding onto.
         if (userId == UserHandle.myUserId()) {
-            if (sUserKeyUnlocked) {
+            if (sCeStorageUnlocked) {
                 return;
-            } else if (isUserKeyUnlocked(userId)) {
-                sUserKeyUnlocked = true;
+            } else if (isCeStorageUnlocked(userId)) {
+                sCeStorageUnlocked = true;
                 return;
             }
-        } else if (isUserKeyUnlocked(userId)) {
+        } else if (isCeStorageUnlocked(userId)) {
             return;
         }
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 159394b..bbfd593 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -23,6 +23,7 @@
 import android.accounts.AccountManager;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -58,6 +59,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.location.LocationManager;
+import android.nfc.Flags;
 import android.provider.Settings;
 import android.util.AndroidException;
 import android.util.ArraySet;
@@ -1831,6 +1833,7 @@
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_USER_RESTRICTION)
     public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO =
             "no_near_field_communication_radio";
 
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 77229c4..4031153 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -1,6 +1,13 @@
 package: "android.os"
 
 flag {
+    name: "android_os_build_vanilla_ice_cream"
+    namespace: "build"
+    description: "Feature flag for adding the VANILLA_ICE_CREAM constant."
+    bug: "264658905"
+}
+
+flag {
     name: "state_of_health_public"
     namespace: "system_sw_battery"
     description: "Feature flag for making state_of_health a public api."
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index bc52744..25b2aa7 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -134,20 +134,20 @@
     @EnforcePermission("MOUNT_UNMOUNT_FILESYSTEMS")
     void setDebugFlags(int flags, int mask) = 60;
     @EnforcePermission("STORAGE_INTERNAL")
-    void createUserKey(int userId, int serialNumber, boolean ephemeral) = 61;
+    void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) = 61;
     @EnforcePermission("STORAGE_INTERNAL")
-    void destroyUserKey(int userId) = 62;
+    void destroyUserStorageKeys(int userId) = 62;
     @EnforcePermission("STORAGE_INTERNAL")
-    void unlockUserKey(int userId, int serialNumber, in byte[] secret) = 63;
+    void unlockCeStorage(int userId, int serialNumber, in byte[] secret) = 63;
     @EnforcePermission("STORAGE_INTERNAL")
-    void lockUserKey(int userId) = 64;
-    boolean isUserKeyUnlocked(int userId) = 65;
+    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;
     @EnforcePermission("STORAGE_INTERNAL")
     void destroyUserStorage(in String volumeUuid, int userId, int flags) = 67;
     @EnforcePermission("STORAGE_INTERNAL")
-    void setUserKeyProtection(int userId, in byte[] secret) = 70;
+    void setCeStorageProtection(int userId, in byte[] secret) = 70;
     @EnforcePermission("MOUNT_FORMAT_FILESYSTEMS")
     void fstrim(int flags, IVoldTaskListener listener) = 72;
     AppFuseMount mountProxyFileDescriptorBridge() = 73;
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 80dd488..8e72e13 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1580,28 +1580,64 @@
                 DEFAULT_FULL_THRESHOLD_BYTES);
     }
 
-    /** {@hide} */
-    public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
+    /**
+     * Creates the keys for a user's credential-encrypted (CE) and device-encrypted (DE) storage.
+     * <p>
+     * This creates the user's CE key and DE key for internal storage, then adds them to the kernel.
+     * Then, if the user is not ephemeral, this stores the DE key (encrypted) on flash.  (The CE key
+     * is not stored until {@link IStorageManager#setCeStorageProtection()}.)
+     * <p>
+     * This does not create the CE and DE directories themselves.  For that, see {@link
+     * #prepareUserStorage()}.
+     * <p>
+     * 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) {
         try {
-            mStorageManager.createUserKey(userId, serialNumber, ephemeral);
+            mStorageManager.createUserStorageKeys(userId, serialNumber, ephemeral);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
-    /** {@hide} */
-    public void destroyUserKey(int userId) {
+    /**
+     * Destroys the keys for a user's credential-encrypted (CE) and device-encrypted (DE) storage.
+     * <p>
+     * This evicts the keys from the kernel (if present), which "locks" the corresponding
+     * directories.  Then, this deletes the encrypted keys from flash.  This operates on all the
+     * user's CE and DE keys, for both internal and adoptable storage.
+     * <p>
+     * This does not destroy the CE and DE directories themselves.  For that, see {@link
+     * #destroyUserStorage()}.
+     * <p>
+     * This is only intended to be called by UserManagerService, as part of removing a user.
+     *
+     * @param userId ID of the user
+     * @throws RuntimeException on error.  On error, as many things as possible are still destroyed.
+     * @hide
+     */
+    public void destroyUserStorageKeys(int userId) {
         try {
-            mStorageManager.destroyUserKey(userId);
+            mStorageManager.destroyUserStorageKeys(userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
-    /** {@hide} */
-    public void lockUserKey(int userId) {
+    /**
+     * Locks the user's credential-encrypted (CE) storage.
+     *
+     * @hide
+     */
+    public void lockCeStorage(int userId) {
         try {
-            mStorageManager.lockUserKey(userId);
+            mStorageManager.lockCeStorage(userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1628,17 +1664,26 @@
     /** {@hide} */
     @TestApi
     public static boolean isUserKeyUnlocked(int userId) {
+        return isCeStorageUnlocked(userId);
+    }
+
+    /**
+     * Returns true if the user's credential-encrypted (CE) storage is unlocked.
+     *
+     * @hide
+     */
+    public static boolean isCeStorageUnlocked(int userId) {
         if (sStorageManager == null) {
             sStorageManager = IStorageManager.Stub
                     .asInterface(ServiceManager.getService("mount"));
         }
         if (sStorageManager == null) {
-            Slog.w(TAG, "Early during boot, assuming locked");
+            Slog.w(TAG, "Early during boot, assuming CE storage is locked");
             return false;
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            return sStorageManager.isUserKeyUnlocked(userId);
+            return sStorageManager.isCeStorageUnlocked(userId);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         } finally {
diff --git a/core/java/android/preference/OWNERS b/core/java/android/preference/OWNERS
index 827134e..b4cb9ec 100644
--- a/core/java/android/preference/OWNERS
+++ b/core/java/android/preference/OWNERS
@@ -1,3 +1,5 @@
 lpf@google.com
 pavlis@google.com
 clarabayarri@google.com
+
+per-file SeekBarVolumizer.java = jmtrivi@google.com
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9cdb9ce..2f86efe 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9926,6 +9926,13 @@
         public static final String SPATIAL_AUDIO_ENABLED = "spatial_audio_enabled";
 
         /**
+         * Internal collection of audio device inventory items
+         * The device item stored are {@link com.android.server.audio.AdiDeviceState}
+         * @hide
+         */
+        public static final String AUDIO_DEVICE_INVENTORY = "audio_device_inventory";
+
+        /**
          * Indicates whether notification display on the lock screen is enabled.
          * <p>
          * Type: int (0 for false, 1 for true)
@@ -17965,6 +17972,15 @@
                 "review_permissions_notification_state";
 
         /**
+         * Whether repair mode is active on the device.
+         * <p>
+         * Set to 1 for true and 0 for false.
+         *
+         * @hide
+         */
+        public static final String REPAIR_MODE_ACTIVE = "repair_mode_active";
+
+        /**
          * Settings migrated from Wear OS settings provider.
          * @hide
          */
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 96c0be7..33a67ae 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -8,3 +8,4 @@
 per-file Confirmation*.java = file:/keystore/OWNERS
 per-file FileIntegrityManager.java = file:platform/system/security:/fsverity/OWNERS
 per-file IFileIntegrityService.aidl = file:platform/system/security:/fsverity/OWNERS
+per-file *.aconfig = victorhsieh@google.com
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b6c2b83..5aa3097 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -12,4 +12,5 @@
     namespace: "hardware_backed_security"
     description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
     bug: "296464083"
+    is_fixed_read_only: true
 }
diff --git a/core/java/android/service/rotationresolver/OWNERS b/core/java/android/service/rotationresolver/OWNERS
index 5b57fc7..dce874d 100644
--- a/core/java/android/service/rotationresolver/OWNERS
+++ b/core/java/android/service/rotationresolver/OWNERS
@@ -1,9 +1,7 @@
 # Bug component: 814982
 
 asalo@google.com
-augale@google.com
 eejiang@google.com
 payamp@google.com
 siddikap@google.com
-svetoslavganov@google.com
 tgadh@google.com
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index 42ac74c..ad326e4 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -11,6 +11,7 @@
 roosa@google.com
 jreck@google.com
 siyamed@google.com
+mount@google.com
 
 # Autofill
 per-file ViewStructure.java = file:/core/java/android/service/autofill/OWNERS
diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS
index 7f0a651..e20357fa 100644
--- a/core/java/android/widget/OWNERS
+++ b/core/java/android/widget/OWNERS
@@ -12,6 +12,6 @@
 
 per-file SpellChecker.java = file:../view/inputmethod/OWNERS
 
-per-file RemoteViews* = file:../appwidget/OWNERS
+per-file Remote* = file:../appwidget/OWNERS
 
 per-file Toast.java = juliacr@google.com, jeffdq@google.com
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index 25bf85c..ec79219 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -21,11 +21,13 @@
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.IBinder;
 import android.os.InputConfig;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseArray;
 import android.view.InputWindowHandle;
 
 import java.util.ArrayList;
@@ -119,13 +121,13 @@
                             "Exception thrown while waiting for listener to be called with "
                                     + "initial state");
                 }
-                consumer.accept(buildWindowInfos(windowHandles));
+                consumer.accept(buildWindowInfos(windowHandles, displayInfos));
             }
         };
         mListeners.put(consumer, listener);
         Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> initialState =
                 listener.register();
-        consumer.accept(buildWindowInfos(initialState.first));
+        consumer.accept(buildWindowInfos(initialState.first, initialState.second));
         calledWithInitialState.countDown();
     }
 
@@ -140,11 +142,28 @@
         listener.unregister();
     }
 
-    private static List<WindowInfo> buildWindowInfos(InputWindowHandle[] windowHandles) {
+    private static List<WindowInfo> buildWindowInfos(
+            InputWindowHandle[] windowHandles, WindowInfosListener.DisplayInfo[] displayInfos) {
         var windowInfos = new ArrayList<WindowInfo>(windowHandles.length);
+
+        var displayInfoById = new SparseArray<WindowInfosListener.DisplayInfo>(displayInfos.length);
+        for (var displayInfo : displayInfos) {
+            displayInfoById.put(displayInfo.mDisplayId, displayInfo);
+        }
+
+        var tmp = new RectF();
         for (var handle : windowHandles) {
             var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
                     handle.frameBottom);
+
+            // Transform bounds from physical display coordinates to logical display coordinates.
+            var display = displayInfoById.get(handle.displayId);
+            if (display != null) {
+                tmp.set(bounds);
+                display.mTransform.mapRect(tmp);
+                tmp.round(bounds);
+            }
+
             windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, handle.displayId,
                     bounds, handle.inputConfig));
         }
diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS
new file mode 100644
index 0000000..fa81ee3
--- /dev/null
+++ b/core/java/android/window/flags/OWNERS
@@ -0,0 +1 @@
+per-file responsible_apis.aconfig = file:/BAL_OWNERS
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 4e7bfe5..71bbccb 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -259,7 +259,7 @@
             }
             return true;
         }
-        return false;
+        return super.onKeyDown(keyCode,event);
     }
 
     @Override
@@ -268,7 +268,7 @@
             stopWarp();
             return true;
         }
-        return false;
+        return super.onKeyUp(keyCode,event);
     }
 
     private void startWarp() {
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 965277c..1c5f4f0 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -868,6 +868,11 @@
                                  args.mPkgDataInfoList, args.mAllowlistedDataInfoList,
                                  args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs);
 
+            // While `specializeAppProcess` sets the thread name on the process's main thread, this
+            // is distinct from the app process name which appears in stack traces, as the latter is
+            // sourced from the argument buffer of the Process class. Set the app process name here.
+            Zygote.setAppProcessName(args, TAG);
+
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
             return ZygoteInit.zygoteInit(args.mTargetSdkVersion,
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 993e4e7..5fe086d 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -296,7 +296,6 @@
                     } else {
                         // child; result is a Runnable.
                         zygoteServer.setForkChild();
-                        Zygote.setAppProcessName(parsedArgs, TAG);  // ??? Necessary?
                         return result;
                     }
                 }
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
deleted file mode 100644
index 929c9e8..0000000
--- a/core/java/com/android/internal/util/FastXmlSerializer.java
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * Copyright (C) 2006 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.util;
-
-import android.compat.annotation.UnsupportedAppUsage;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CoderResult;
-import java.nio.charset.CodingErrorAction;
-import java.nio.charset.IllegalCharsetNameException;
-import java.nio.charset.UnsupportedCharsetException;
-
-/**
- * This is a quick and dirty implementation of XmlSerializer that isn't horribly
- * painfully slow like the normal one.  It only does what is needed for the
- * specific XML files being written with it.
- */
-public class FastXmlSerializer implements XmlSerializer {
-    private static final String ESCAPE_TABLE[] = new String[] {
-        "&#0;",   "&#1;",   "&#2;",   "&#3;",  "&#4;",    "&#5;",   "&#6;",  "&#7;",  // 0-7
-        "&#8;",   "&#9;",   "&#10;",  "&#11;", "&#12;",   "&#13;",  "&#14;", "&#15;", // 8-15
-        "&#16;",  "&#17;",  "&#18;",  "&#19;", "&#20;",   "&#21;",  "&#22;", "&#23;", // 16-23
-        "&#24;",  "&#25;",  "&#26;",  "&#27;", "&#28;",   "&#29;",  "&#30;", "&#31;", // 24-31
-        null,     null,     "&quot;", null,     null,     null,     "&amp;",  null,   // 32-39
-        null,     null,     null,     null,     null,     null,     null,     null,   // 40-47
-        null,     null,     null,     null,     null,     null,     null,     null,   // 48-55
-        null,     null,     null,     null,     "&lt;",   null,     "&gt;",   null,   // 56-63
-    };
-
-    private static final int DEFAULT_BUFFER_LEN = 32*1024;
-
-    private static String sSpace = "                                                              ";
-
-    private final int mBufferLen;
-    private final char[] mText;
-    private int mPos;
-
-    private Writer mWriter;
-
-    private OutputStream mOutputStream;
-    private CharsetEncoder mCharset;
-    private ByteBuffer mBytes;
-
-    private boolean mIndent = false;
-    private boolean mInTag;
-
-    private int mNesting = 0;
-    private boolean mLineStart = true;
-
-    @UnsupportedAppUsage
-    public FastXmlSerializer() {
-        this(DEFAULT_BUFFER_LEN);
-    }
-
-    /**
-     * Allocate a FastXmlSerializer with the given internal output buffer size.  If the
-     * size is zero or negative, then the default buffer size will be used.
-     *
-     * @param bufferSize Size in bytes of the in-memory output buffer that the writer will use.
-     */
-    public FastXmlSerializer(int bufferSize) {
-        mBufferLen = (bufferSize > 0) ? bufferSize : DEFAULT_BUFFER_LEN;
-        mText = new char[mBufferLen];
-        mBytes = ByteBuffer.allocate(mBufferLen);
-    }
-
-    private void append(char c) throws IOException {
-        int pos = mPos;
-        if (pos >= (mBufferLen-1)) {
-            flush();
-            pos = mPos;
-        }
-        mText[pos] = c;
-        mPos = pos+1;
-    }
-
-    private void append(String str, int i, final int length) throws IOException {
-        if (length > mBufferLen) {
-            final int end = i + length;
-            while (i < end) {
-                int next = i + mBufferLen;
-                append(str, i, next<end ? mBufferLen : (end-i));
-                i = next;
-            }
-            return;
-        }
-        int pos = mPos;
-        if ((pos+length) > mBufferLen) {
-            flush();
-            pos = mPos;
-        }
-        str.getChars(i, i+length, mText, pos);
-        mPos = pos + length;
-    }
-
-    private void append(char[] buf, int i, final int length) throws IOException {
-        if (length > mBufferLen) {
-            final int end = i + length;
-            while (i < end) {
-                int next = i + mBufferLen;
-                append(buf, i, next<end ? mBufferLen : (end-i));
-                i = next;
-            }
-            return;
-        }
-        int pos = mPos;
-        if ((pos+length) > mBufferLen) {
-            flush();
-            pos = mPos;
-        }
-        System.arraycopy(buf, i, mText, pos, length);
-        mPos = pos + length;
-    }
-
-    private void append(String str) throws IOException {
-        append(str, 0, str.length());
-    }
-
-    private void appendIndent(int indent) throws IOException {
-        indent *= 4;
-        if (indent > sSpace.length()) {
-            indent = sSpace.length();
-        }
-        append(sSpace, 0, indent);
-    }
-
-    private void escapeAndAppendString(final String string) throws IOException {
-        final int N = string.length();
-        final char NE = (char)ESCAPE_TABLE.length;
-        final String[] escapes = ESCAPE_TABLE;
-        int lastPos = 0;
-        int pos;
-        for (pos=0; pos<N; pos++) {
-            char c = string.charAt(pos);
-            if (c >= NE) continue;
-            String escape = escapes[c];
-            if (escape == null) continue;
-            if (lastPos < pos) append(string, lastPos, pos-lastPos);
-            lastPos = pos + 1;
-            append(escape);
-        }
-        if (lastPos < pos) append(string, lastPos, pos-lastPos);
-    }
-
-    private void escapeAndAppendString(char[] buf, int start, int len) throws IOException {
-        final char NE = (char)ESCAPE_TABLE.length;
-        final String[] escapes = ESCAPE_TABLE;
-        int end = start+len;
-        int lastPos = start;
-        int pos;
-        for (pos=start; pos<end; pos++) {
-            char c = buf[pos];
-            if (c >= NE) continue;
-            String escape = escapes[c];
-            if (escape == null) continue;
-            if (lastPos < pos) append(buf, lastPos, pos-lastPos);
-            lastPos = pos + 1;
-            append(escape);
-        }
-        if (lastPos < pos) append(buf, lastPos, pos-lastPos);
-    }
-
-    public XmlSerializer attribute(String namespace, String name, String value) throws IOException,
-            IllegalArgumentException, IllegalStateException {
-        append(' ');
-        if (namespace != null) {
-            append(namespace);
-            append(':');
-        }
-        append(name);
-        append("=\"");
-
-        escapeAndAppendString(value);
-        append('"');
-        mLineStart = false;
-        return this;
-    }
-
-    public void cdsect(String text) throws IOException, IllegalArgumentException,
-            IllegalStateException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void comment(String text) throws IOException, IllegalArgumentException,
-            IllegalStateException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void docdecl(String text) throws IOException, IllegalArgumentException,
-            IllegalStateException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException {
-        flush();
-    }
-
-    public XmlSerializer endTag(String namespace, String name) throws IOException,
-            IllegalArgumentException, IllegalStateException {
-        mNesting--;
-        if (mInTag) {
-            append(" />\n");
-        } else {
-            if (mIndent && mLineStart) {
-                appendIndent(mNesting);
-            }
-            append("</");
-            if (namespace != null) {
-                append(namespace);
-                append(':');
-            }
-            append(name);
-            append(">\n");
-        }
-        mLineStart = true;
-        mInTag = false;
-        return this;
-    }
-
-    public void entityRef(String text) throws IOException, IllegalArgumentException,
-            IllegalStateException {
-        throw new UnsupportedOperationException();
-    }
-
-    private void flushBytes() throws IOException {
-        int position;
-        if ((position = mBytes.position()) > 0) {
-            mBytes.flip();
-            mOutputStream.write(mBytes.array(), 0, position);
-            mBytes.clear();
-        }
-    }
-
-    public void flush() throws IOException {
-        //Log.i("PackageManager", "flush mPos=" + mPos);
-        if (mPos > 0) {
-            if (mOutputStream != null) {
-                CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos);
-                CoderResult result = mCharset.encode(charBuffer, mBytes, true);
-                while (true) {
-                    if (result.isError()) {
-                        throw new IOException(result.toString());
-                    } else if (result.isOverflow()) {
-                        flushBytes();
-                        result = mCharset.encode(charBuffer, mBytes, true);
-                        continue;
-                    }
-                    break;
-                }
-                flushBytes();
-                mOutputStream.flush();
-            } else {
-                mWriter.write(mText, 0, mPos);
-                mWriter.flush();
-            }
-            mPos = 0;
-        }
-    }
-
-    public int getDepth() {
-        throw new UnsupportedOperationException();
-    }
-
-    public boolean getFeature(String name) {
-        throw new UnsupportedOperationException();
-    }
-
-    public String getName() {
-        throw new UnsupportedOperationException();
-    }
-
-    public String getNamespace() {
-        throw new UnsupportedOperationException();
-    }
-
-    public String getPrefix(String namespace, boolean generatePrefix)
-            throws IllegalArgumentException {
-        throw new UnsupportedOperationException();
-    }
-
-    public Object getProperty(String name) {
-        throw new UnsupportedOperationException();
-    }
-
-    public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException,
-            IllegalStateException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void processingInstruction(String text) throws IOException, IllegalArgumentException,
-            IllegalStateException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void setFeature(String name, boolean state) throws IllegalArgumentException,
-            IllegalStateException {
-        if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) {
-            mIndent = true;
-            return;
-        }
-        throw new UnsupportedOperationException();
-    }
-
-    public void setOutput(OutputStream os, String encoding) throws IOException,
-            IllegalArgumentException, IllegalStateException {
-        if (os == null)
-            throw new IllegalArgumentException();
-        if (true) {
-            try {
-                mCharset = Charset.forName(encoding).newEncoder()
-                        .onMalformedInput(CodingErrorAction.REPLACE)
-                        .onUnmappableCharacter(CodingErrorAction.REPLACE);
-            } catch (IllegalCharsetNameException e) {
-                throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
-                        encoding).initCause(e));
-            } catch (UnsupportedCharsetException e) {
-                throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
-                        encoding).initCause(e));
-            }
-            mOutputStream = os;
-        } else {
-            setOutput(
-                encoding == null
-                    ? new OutputStreamWriter(os)
-                    : new OutputStreamWriter(os, encoding));
-        }
-    }
-
-    public void setOutput(Writer writer) throws IOException, IllegalArgumentException,
-            IllegalStateException {
-        mWriter = writer;
-    }
-
-    public void setPrefix(String prefix, String namespace) throws IOException,
-            IllegalArgumentException, IllegalStateException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void setProperty(String name, Object value) throws IllegalArgumentException,
-            IllegalStateException {
-        throw new UnsupportedOperationException();
-    }
-
-    public void startDocument(String encoding, Boolean standalone) throws IOException,
-            IllegalArgumentException, IllegalStateException {
-        append("<?xml version='1.0' encoding='utf-8'");
-        if (standalone != null) {
-            append(" standalone='" + (standalone ? "yes" : "no") + "'");
-        }
-        append(" ?>\n");
-        mLineStart = true;
-    }
-
-    public XmlSerializer startTag(String namespace, String name) throws IOException,
-            IllegalArgumentException, IllegalStateException {
-        if (mInTag) {
-            append(">\n");
-        }
-        if (mIndent) {
-            appendIndent(mNesting);
-        }
-        mNesting++;
-        append('<');
-        if (namespace != null) {
-            append(namespace);
-            append(':');
-        }
-        append(name);
-        mInTag = true;
-        mLineStart = false;
-        return this;
-    }
-
-    public XmlSerializer text(char[] buf, int start, int len) throws IOException,
-            IllegalArgumentException, IllegalStateException {
-        if (mInTag) {
-            append(">");
-            mInTag = false;
-        }
-        escapeAndAppendString(buf, start, len);
-        if (mIndent) {
-            mLineStart = buf[start+len-1] == '\n';
-        }
-        return this;
-    }
-
-    public XmlSerializer text(String text) throws IOException, IllegalArgumentException,
-            IllegalStateException {
-        if (mInTag) {
-            append(">");
-            mInTag = false;
-        }
-        escapeAndAppendString(text);
-        if (mIndent) {
-            mLineStart = text.length() > 0 && (text.charAt(text.length()-1) == '\n');
-        }
-        return this;
-    }
-
-}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 4065055..8236783 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -108,4 +108,5 @@
     boolean removeWeakEscrowToken(long handle, int userId);
     boolean isWeakEscrowTokenActive(long handle, int userId);
     boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId);
+    void unlockUserKeyIfUnsecured(int userId);
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index a1f8de4..a3e0016 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -47,6 +47,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
@@ -160,9 +161,17 @@
      */
     public static final int VERIFY_FLAG_REQUEST_GK_PW_HANDLE = 1 << 0;
 
+    /**
+     * Flag provided to {@link #verifyCredential(LockscreenCredential, int, int)} . If set, the
+     * method writes the password data to the repair mode file after the credential is verified
+     * successfully.
+     */
+    public static final int VERIFY_FLAG_WRITE_REPAIR_MODE_PW = 1 << 1;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {
-            VERIFY_FLAG_REQUEST_GK_PW_HANDLE
+            VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
+            VERIFY_FLAG_WRITE_REPAIR_MODE_PW
     })
     public @interface VerifyFlag {}
 
@@ -171,6 +180,11 @@
      */
     public static final int USER_FRP = UserHandle.USER_NULL + 1;
 
+    /**
+     * Special user id for triggering the exiting repair mode verification flow.
+     */
+    public static final int USER_REPAIR_MODE = UserHandle.USER_NULL + 2;
+
     public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
     @Deprecated
     public final static String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
@@ -200,6 +214,8 @@
     public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
     public static final String PASSWORD_HISTORY_DELIMITER = ",";
 
+    private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
+
     /**
      * drives the pin auto confirmation feature availability in code logic.
      */
@@ -388,7 +404,7 @@
 
     @UnsupportedAppUsage
     public void reportFailedPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return;
         }
         getDevicePolicyManager().reportFailedPasswordAttempt(userId);
@@ -397,7 +413,7 @@
 
     @UnsupportedAppUsage
     public void reportSuccessfulPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return;
         }
         getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
@@ -405,21 +421,21 @@
     }
 
     public void reportPasswordLockout(int timeoutMs, int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return;
         }
         getTrustManager().reportUnlockLockout(timeoutMs, userId);
     }
 
     public int getCurrentFailedPasswordAttempts(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return 0;
         }
         return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
     }
 
     public int getMaximumFailedPasswordsForWipe(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return 0;
         }
         return getDevicePolicyManager().getMaximumFailedPasswordsForWipe(
@@ -739,6 +755,17 @@
         }
     }
 
+    /** Returns the credential type corresponding to the given PIN or password quality. */
+    public static int pinOrPasswordQualityToCredentialType(int quality) {
+        if (isQualityAlphabeticPassword(quality)) {
+            return CREDENTIAL_TYPE_PASSWORD;
+        }
+        if (isQualityNumericPin(quality)) {
+            return CREDENTIAL_TYPE_PIN;
+        }
+        throw new IllegalArgumentException("Quality is neither Pin nor password: " + quality);
+    }
+
     /**
      * Save a new lockscreen credential.
      *
@@ -971,7 +998,7 @@
                 }
                 @Override
                 public boolean shouldBypassCache(Integer userHandle) {
-                    return userHandle == USER_FRP;
+                    return isSpecialUserId(userHandle);
                 }
             };
 
@@ -1819,6 +1846,64 @@
     }
 
     /**
+     * Return {@code true} if repair mode is supported by the device.
+     */
+    public static boolean isRepairModeSupported(Context context) {
+        return context.getResources().getBoolean(
+                com.android.internal.R.bool.config_repairModeSupported);
+    }
+
+    /**
+     * Return {@code true} if repair mode is active on the device.
+     */
+    public static boolean isRepairModeActive(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.REPAIR_MODE_ACTIVE, /* def= */ 0) > 0;
+    }
+
+    /**
+     * Return {@code true} if repair mode is supported by the device and the user has been granted
+     * admin privileges.
+     */
+    public static boolean canUserEnterRepairMode(Context context, UserInfo info) {
+        return info != null && info.isAdmin() && isRepairModeSupported(context);
+    }
+
+    /**
+     * Return {@code true} if GSI is running on the device.
+     */
+    public static boolean isGsiRunning() {
+        return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
+    }
+
+    /**
+     * Return {@code true} if the given user id is a special user such as {@link #USER_FRP}.
+     */
+    public static boolean isSpecialUserId(int userId) {
+        return isSpecialUserId(/* context= */ null, userId, /* checkDeviceSupported= */ false);
+    }
+
+    /**
+     * Return {@code true} if the given user id is a special user for the verification flow.
+     *
+     * @param checkDeviceSupported {@code true} to check the specified user is supported
+     *                             by the device.
+     */
+    private static boolean isSpecialUserId(@Nullable Context context, int userId,
+            boolean checkDeviceSupported) {
+        switch (userId) {
+            case USER_FRP:
+                if (checkDeviceSupported) return frpCredentialEnabled(context);
+                return true;
+
+            case USER_REPAIR_MODE:
+                if (checkDeviceSupported) return isRepairModeSupported(context);
+                return true;
+        }
+        return false;
+    }
+
+    /**
      * Attempt to rederive the unified work challenge for the specified profile user and unlock the
      * user. If successful, this would allow the user to leave quiet mode automatically without
      * additional user authentication.
@@ -1847,8 +1932,29 @@
         }
     }
 
+    /**
+     * If the user is not secured, ie doesn't have an LSKF, then decrypt the user's synthetic
+     * password and use it to unlock various cryptographic keys associated with the user.  This
+     * primarily includes unlocking the user's credential-encrypted (CE) storage.  It also includes
+     * deriving or decrypting the vendor auth secret and sending it to the AuthSecret HAL.
+     * <p>
+     * These tasks would normally be done when the LSKF is verified.  This method is where these
+     * tasks are done when the user doesn't have an LSKF.  It's called when the user is started.
+     * <p>
+     * Except on permission denied, this method doesn't throw an exception on failure.  However, the
+     * last thing that it does is unlock CE storage, and whether CE storage has been successfully
+     * unlocked can be determined by {@link StorageManager#isCeStorageUnlocked()}.
+     * <p>
+     * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+     *
+     * @param userId the ID of the user whose keys to unlock
+     */
     public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
-        getLockSettingsInternal().unlockUserKeyIfUnsecured(userId);
+        try {
+            getLockSettings().unlockUserKeyIfUnsecured(userId);
+        } catch (RemoteException re) {
+            re.rethrowFromSystemServer();
+        }
     }
 
     public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 6063c90..8114e1f 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -60,17 +60,6 @@
     public abstract void onThirdPartyAppsStarted();
 
     /**
-     * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
-     * doesn't have an LSKF.
-     * <p>
-     * This doesn't throw an exception on failure; whether the storage has been unlocked can be
-     * determined by {@link StorageManager#isUserKeyUnlocked()}.
-     *
-     * @param userId the ID of the user whose storage to unlock
-     */
-    public abstract void unlockUserKeyIfUnsecured(@UserIdInt int userId);
-
-    /**
      * Creates the locksettings state for a new user.
      * <p>
      * This includes creating a synthetic password and protecting it with an empty LSKF.
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index b1dab85..8fa179b 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -574,7 +574,7 @@
     if (result != NO_ERROR) {
         return -1;
     }
-    return frameCount * channelCount * audio_bytes_per_sample(format);
+    return frameCount * audio_bytes_per_frame(channelCount, format);
 }
 
 static jboolean android_media_AudioRecord_setInputDevice(
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 01dbceb..8fc30d1 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -16,11 +16,12 @@
 
 #define LOG_TAG "GraphicsEnvironment"
 
-#include <vector>
-
 #include <graphicsenv/GraphicsEnv.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativeloader/native_loader.h>
+
+#include <vector>
+
 #include "core_jni_helpers.h"
 
 namespace {
@@ -49,11 +50,10 @@
                                                     appPackageNameChars.c_str(), vulkanVersion);
 }
 
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName,
-                         jstring devOptIn, jobjectArray featuresObj) {
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver,
+                         jstring packageName, jobjectArray featuresObj) {
     ScopedUtfChars pathChars(env, path);
-    ScopedUtfChars appNameChars(env, appName);
-    ScopedUtfChars devOptInChars(env, devOptIn);
+    ScopedUtfChars packageNameChars(env, packageName);
 
     std::vector<std::string> features;
     if (featuresObj != nullptr) {
@@ -73,13 +73,8 @@
         }
     }
 
-    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(),
-                                                     devOptInChars.c_str(), features);
-}
-
-bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) {
-    ScopedUtfChars appNameChars(env, appName);
-    return android::GraphicsEnv::getInstance().shouldUseAngle(appNameChars.c_str());
+    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver,
+                                                     packageNameChars.c_str(), features);
 }
 
 void setLayerPaths_native(JNIEnv* env, jobject clazz, jobject classLoader, jstring layerPaths) {
@@ -123,11 +118,8 @@
          reinterpret_cast<void*>(setGpuStats_native)},
         {"setInjectLayersPrSetDumpable", "()Z",
          reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
-        {"setAngleInfo",
-         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",
+        {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
          reinterpret_cast<void*>(setAngleInfo_native)},
-        {"getShouldUseAngle", "(Ljava/lang/String;)Z",
-         reinterpret_cast<void*>(shouldUseAngle_native)},
         {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
          reinterpret_cast<void*>(setLayerPaths_native)},
         {"setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native)},
diff --git a/core/proto/android/nfc/Android.bp b/core/proto/android/nfc/Android.bp
deleted file mode 100644
index 6a62c91..0000000
--- a/core/proto/android/nfc/Android.bp
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
-    name: "srcs_nfc_proto",
-    srcs: [
-        "*.proto",
-    ],
-}
-
-// Will be statically linked by `framework-nfc`.
-java_library {
-    name: "nfc-proto-java-gen",
-    installable: false,
-    proto: {
-        type: "stream",
-        include_dirs: [
-            "external/protobuf/src",
-        ],
-    },
-    srcs: [
-        ":srcs_nfc_proto",
-    ],
-    sdk_version: "current",
-    min_sdk_version: "current",
-}
diff --git a/core/proto/android/service/OWNERS b/core/proto/android/service/OWNERS
index 70cb50f..7a19155 100644
--- a/core/proto/android/service/OWNERS
+++ b/core/proto/android/service/OWNERS
@@ -1 +1,2 @@
 per-file sensor_service.proto = arthuri@google.com, bduddie@google.com, stange@google.com
+per-file package.proto = file:platform/frameworks/base:/PACKAGE_MANAGER_OWNERS
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7d9d991..0e753e5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2225,6 +2225,13 @@
     <permission android:name="android.permission.MANAGE_LOWPAN_INTERFACES"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows changing Thread network state and access to Thread network
+        credentials such as Network Key and PSKc.
+        <p>Not for use by third-party applications.
+        @FlaggedApi("com.android.net.thread.flags.thread_enabled") -->
+    <permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED"
+                android:protectionLevel="signature|privileged" />
+
     <!-- #SystemApi @hide Allows an app to bypass Private DNS.
          <p>Not for use by third-party applications.
          TODO: publish as system API in next API release. -->
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 0df7c20..f24c3f5 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -1,5 +1,6 @@
 adamp@google.com
 asc@google.com
+austindelgado@google.com
 cinek@google.com
 dsandler@android.com
 dsandler@google.com
@@ -8,6 +9,7 @@
 hackbod@google.com
 ilyamaty@google.com
 jaggies@google.com
+jbolinger@google.com
 jsharkey@android.com
 jsharkey@google.com
 juliacr@google.com
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 93048ea..a872efe 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6467,4 +6467,9 @@
     <!-- Whether the AOSP support for app cloning building blocks is to be enabled for the
          device. -->
     <bool name="config_enableAppCloningBuildingBlocks">true</bool>
+
+    <!-- Enables or disables support for repair mode. The feature creates a secure
+         environment to protect the user's privacy when the device is being repaired.
+         Off by default, since OEMs may have had a similar feature on their devices. -->
+    <bool name="config_repairModeSupported">false</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9bb3387..e81dfe7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4917,6 +4917,8 @@
 
   <java-symbol type="bool" name="config_safetyProtectionEnabled" />
 
+  <java-symbol type="bool" name="config_repairModeSupported" />
+
   <java-symbol type="string" name="config_devicePolicyManagementUpdater" />
 
   <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index af8c69e..3a2e50a 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -54,6 +54,9 @@
     <!-- Azerbaijan: 4-5 digits, known premium codes listed -->
     <shortcode country="az" pattern="\\d{4,5}" premium="330[12]|87744|901[234]|93(?:94|101)|9426|9525" />
 
+    <!-- Bangladesh: 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="bd" pattern="\\d{1,5}" free="16672" />
+
     <!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp -->
     <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" />
 
@@ -145,7 +148,7 @@
     <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />
 
     <!-- Indonesia: 1-5 digits (standard system default, not country specific) -->
-    <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363" />
+    <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457" />
 
     <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:
          http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
@@ -190,7 +193,7 @@
     <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" />
+    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" />
 
     <!-- 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" />
@@ -205,7 +208,7 @@
     <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" />
 
     <!-- New Zealand: 3-4 digits, known premium codes listed -->
-    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
+    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|3876|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
 
     <!-- Peru: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" />
diff --git a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
index fac3a0e..d34c91e 100644
--- a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
+++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
@@ -86,16 +86,6 @@
         GameModeInfo gameModeInfo = mGameManager.getGameModeInfo(mPackageName);
         assertNotNull(gameModeInfo);
         assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
-        GameModeConfiguration unsupportedFpsConfig =
-                new GameModeConfiguration.Builder().setFpsOverride(
-                        70).setScalingFactor(0.5f).build();
-        mGameManager.updateCustomGameModeConfiguration(mPackageName, unsupportedFpsConfig);
-        gameModeInfo = mGameManager.getGameModeInfo(mPackageName);
-        assertNotNull(gameModeInfo);
-        // TODO(b/243448953): update to non-zero FPS when matching is implemented
-        assertEquals(new GameModeConfiguration.Builder().setFpsOverride(
-                        GameModeConfiguration.FPS_OVERRIDE_NONE).setScalingFactor(0.5f).build(),
-                gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_CUSTOM));
 
         GameModeConfiguration supportedFpsConfig =
                 new GameModeConfiguration.Builder().setFpsOverride(
diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS
index 64f5e6c..5636f9b 100644
--- a/core/tests/coretests/src/android/app/OWNERS
+++ b/core/tests/coretests/src/android/app/OWNERS
@@ -10,3 +10,7 @@
 
 # KeyguardManagerTest
 per-file KeyguardManagerTest.java = file:/services/core/java/com/android/server/locksettings/OWNERS
+
+# Files related to background activity launches
+per-file Background*Start* = file:/BAL_OWNERS
+
diff --git a/core/tests/coretests/src/android/content/BrickDeniedTest.java b/core/tests/coretests/src/android/content/BrickDeniedTest.java
deleted file mode 100644
index d8c9baa..0000000
--- a/core/tests/coretests/src/android/content/BrickDeniedTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.test.AndroidTestCase;
-
-import androidx.test.filters.SmallTest;
-
-/** Test to make sure brick intents <b>don't</b> work without permission. */
-public class BrickDeniedTest extends AndroidTestCase {
-    @SmallTest
-    public void testBrick() {
-        // Try both the old and new brick intent names.  Neither should work,
-        // since this test application doesn't have the required permission.
-        // If it does work, well, the test certainly won't pass.
-        getContext().sendBroadcast(new Intent("SHES_A_BRICK_HOUSE"));
-        getContext().sendBroadcast(new Intent("android.intent.action.BRICK"));
-    }
-}
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 2ca9994..23668a4 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -20,4 +20,7 @@
 per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
 
 # Stylus
-per-file stylus/* = file:/core/java/android/text/OWNERS
\ No newline at end of file
+per-file stylus/* = file:/core/java/android/text/OWNERS
+
+# View
+file:/core/java/android/view/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 6167c4b..1a668f7 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -37,10 +37,23 @@
     }
 
     @Test
+    public void testUserRepairMode_isNotRegularUser() {
+        assertTrue(LockPatternUtils.USER_REPAIR_MODE < 0);
+    }
+
+    @Test
     public void testUserFrp_isNotAReservedSpecialUser() throws Exception {
         assertNotEquals(UserHandle.USER_NULL, LockPatternUtils.USER_FRP);
         assertNotEquals(UserHandle.USER_ALL, LockPatternUtils.USER_FRP);
         assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_FRP);
         assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_FRP);
     }
+
+    @Test
+    public void testUserRepairMode_isNotAReservedSpecialUser() throws Exception {
+        assertNotEquals(UserHandle.USER_NULL, LockPatternUtils.USER_REPAIR_MODE);
+        assertNotEquals(UserHandle.USER_ALL, LockPatternUtils.USER_REPAIR_MODE);
+        assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE);
+        assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
+    }
 }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index b05507e..c1018f5 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -438,6 +438,8 @@
         <permission name="android.permission.MANAGE_WIFI_NETWORK_SELECTION" />
         <!-- Permission needed for CTS test - ConcurrencyTest#testP2pSetWfdInfo -->
         <permission name="android.permission.CONFIGURE_WIFI_DISPLAY" />
+        <!-- Permission required for CTS test - CtsThreadNetworkTestCases -->
+        <permission name="android.permission.THREAD_NETWORK_PRIVILEGED"/>
         <!-- Permission required for CTS test CarrierMessagingServiceWrapperTest -->
         <permission name="android.permission.BIND_CARRIER_SERVICES"/>
         <!-- Permission required for CTS test - MusicRecognitionManagerTest -->
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index e27cd97..31092536 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -439,8 +439,10 @@
 key usage 0x0c0173 MEDIA_AUDIO_TRACK
 key usage 0x0c019C PROFILE_SWITCH
 key usage 0x0c01A2 ALL_APPS
-key usage 0x0d0044 STYLUS_BUTTON_PRIMARY
-key usage 0x0d005a STYLUS_BUTTON_SECONDARY
+# 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
 
 # Joystick and game controller axes.
 # Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 31c2eb2..b7ea04f 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -128,25 +128,6 @@
     }
 
     /**
-     * Queries user state from Keystore 2.0.
-     *
-     * @param userId - Android user id of the user.
-     * @return UserState enum variant as integer if successful or an error
-     */
-    public static int getState(int userId) {
-        StrictMode.noteDiskRead();
-        try {
-            return getService().getState(userId);
-        } catch (ServiceSpecificException e) {
-            Log.e(TAG, "getState failed", e);
-            return e.errorCode;
-        } catch (Exception e) {
-            Log.e(TAG, "Can not connect to keystore", e);
-            return SYSTEM_ERROR;
-        }
-    }
-
-    /**
      * Informs Keystore 2.0 that an off body event was detected.
      */
     public static void onDeviceOffBody() {
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 2d2dd24..b4b3e92 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -18,7 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.security.keymint.HardwareAuthToken;
+import android.hardware.security.keymint.HardwareAuthenticatorType;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
@@ -37,7 +39,10 @@
 
     public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR;
 
-    private static IKeystoreAuthorization getService() {
+    /**
+     * @return an instance of IKeystoreAuthorization
+     */
+    public static IKeystoreAuthorization getService() {
         return IKeystoreAuthorization.Stub.asInterface(
                     ServiceManager.checkService("android.security.authorization"));
     }
@@ -100,4 +105,24 @@
         }
     }
 
+    /**
+     * Gets the last authentication time of the given user and authenticators.
+     *
+     * @param userId user id
+     * @param authenticatorTypes an array of {@link HardwareAuthenticatorType}.
+     * @return the last authentication time or
+     * {@link BiometricConstants#BIOMETRIC_NO_AUTHENTICATION}.
+     */
+    public static long getLastAuthenticationTime(
+            long userId, @HardwareAuthenticatorType int[] authenticatorTypes) {
+        try {
+            return getService().getLastAuthTime(userId, authenticatorTypes);
+        } catch (RemoteException | NullPointerException e) {
+            Log.w(TAG, "Can not connect to keystore", e);
+            return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+        } catch (ServiceSpecificException e) {
+            return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+        }
+    }
+
 }
diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java
index af188a9..464714f 100644
--- a/keystore/java/android/security/GateKeeper.java
+++ b/keystore/java/android/security/GateKeeper.java
@@ -45,8 +45,19 @@
 
     @UnsupportedAppUsage
     public static long getSecureUserId() throws IllegalStateException {
+        return getSecureUserId(UserHandle.myUserId());
+    }
+
+    /**
+     * Return the secure user id for a given user id
+     * @param userId the user id, e.g. 0
+     * @return the secure user id or {@link GateKeeper#INVALID_SECURE_USER_ID} if no such mapping
+     * for the given user id is found.
+     * @throws IllegalStateException if there is an error retrieving the secure user id
+     */
+    public static long getSecureUserId(int userId) throws IllegalStateException {
         try {
-            return getService().getSecureUserId(UserHandle.myUserId());
+            return getService().getSecureUserId(userId);
         } catch (RemoteException e) {
             throw new IllegalStateException(
                     "Failed to obtain secure user ID from gatekeeper", e);
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 8045f55..11b8271 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -19,8 +19,6 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.StrictMode;
-import android.os.UserHandle;
-import android.security.maintenance.UserState;
 
 /**
  * @hide This should not be made public in its present form because it
@@ -37,15 +35,6 @@
     // Used for UID field to indicate the calling UID.
     public static final int UID_SELF = -1;
 
-    // States
-    public enum State {
-        @UnsupportedAppUsage
-        UNLOCKED,
-        @UnsupportedAppUsage
-        LOCKED,
-        UNINITIALIZED
-    };
-
     private static final KeyStore KEY_STORE = new KeyStore();
 
     @UnsupportedAppUsage
@@ -55,28 +44,6 @@
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public State state(int userId) {
-        int userState = AndroidKeyStoreMaintenance.getState(userId);
-        switch (userState) {
-            case UserState.UNINITIALIZED:
-                return KeyStore.State.UNINITIALIZED;
-            case UserState.LSKF_UNLOCKED:
-                return KeyStore.State.UNLOCKED;
-            case UserState.LSKF_LOCKED:
-                return KeyStore.State.LOCKED;
-            default:
-                throw new AssertionError(userState);
-        }
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage
-    public State state() {
-        return state(UserHandle.myUserId());
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public byte[] get(String key) {
         return null;
     }
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 96c257b..b714035 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -75,16 +75,18 @@
  * {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey}
  * interfaces.
  *
- * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in
- * the Android Keystore. This is because the {@link java.security.KeyStore} abstraction does not
- * support storing key pairs without a certificate. The subject, serial number, and validity dates
- * of the certificate can be customized in this spec. The self-signed certificate may be replaced at
- * a later time by a certificate signed by a Certificate Authority (CA).
+ * <p>For asymmetric key pairs, a X.509 certificate will be also generated and stored in the Android
+ * Keystore. This is because the {@link java.security.KeyStore} abstraction does not support storing
+ * key pairs without a certificate. The subject, serial number, and validity dates of the
+ * certificate can be customized in this spec. The certificate may be replaced at a later time by a
+ * certificate signed by a Certificate Authority (CA).
  *
- * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the
- * certificate will be created with an invalid signature which will not verify. Such a certificate
- * is still useful because it provides access to the public key. To generate a valid signature for
- * the certificate the key needs to be authorized for all of the following:
+ * <p>NOTE: If attestation is not requested using {@link Builder#setAttestationChallenge(byte[])},
+ * generated certificate may be self-signed. If a private key is not authorized to sign the
+ * certificate, then the certificate will be created with an invalid signature which will not
+ * verify. Such a certificate is still useful because it provides access to the public key. To
+ * generate a valid signature for the certificate the key needs to be authorized for all of the
+ * following:
  * <ul>
  * <li>{@link KeyProperties#PURPOSE_SIGN},</li>
  * <li>operation without requiring the user to be authenticated (see
@@ -989,12 +991,6 @@
          * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be
          *        used. Attempts to use the key for any other purpose will be rejected.
          *
-         *        <p>If the set of purposes for which the key can be used does not contain
-         *        {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by
-         *        {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an
-         *        invalid signature. This is OK if the certificate is only used for obtaining the
-         *        public key from Android KeyStore.
-         *
          *        <p>See {@link KeyProperties}.{@code PURPOSE} flags.
          */
         public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) {
@@ -1140,7 +1136,7 @@
         }
 
         /**
-         * Sets the subject used for the self-signed certificate of the generated key pair.
+         * Sets the subject used for the certificate of the generated key pair.
          *
          * <p>By default, the subject is {@code CN=fake}.
          */
@@ -1154,7 +1150,7 @@
         }
 
         /**
-         * Sets the serial number used for the self-signed certificate of the generated key pair.
+         * Sets the serial number used for the certificate of the generated key pair.
          *
          * <p>By default, the serial number is {@code 1}.
          */
@@ -1168,8 +1164,7 @@
         }
 
         /**
-         * Sets the start of the validity period for the self-signed certificate of the generated
-         * key pair.
+         * Sets the start of the validity period for the certificate of the generated key pair.
          *
          * <p>By default, this date is {@code Jan 1 1970}.
          */
@@ -1183,8 +1178,7 @@
         }
 
         /**
-         * Sets the end of the validity period for the self-signed certificate of the generated key
-         * pair.
+         * Sets the end of the validity period for the certificate of the generated key pair.
          *
          * <p>By default, this date is {@code Jan 1 2048}.
          */
@@ -1602,6 +1596,8 @@
          * {@link #getAttestationChallenge()} returns non-null and the spec is used to generate a
          * symmetric (AES or HMAC) key, {@link javax.crypto.KeyGenerator#generateKey()} will throw
          * {@link java.security.InvalidAlgorithmParameterException}.
+         *
+         * <p>The challenge may be up to 128 bytes.
          */
         @NonNull
         public Builder setAttestationChallenge(byte[] attestationChallenge) {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 06aed63..09d3f05 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -35,7 +35,7 @@
 
 #ifndef __ANDROID__ // Layoutlib does not compile HWUIProperties.sysprop as it depends on cutils properties
 std::optional<bool> use_vulkan() {
-    return base::GetBoolProperty("ro.hwui.use_vulkan", false);
+    return base::GetBoolProperty("ro.hwui.use_vulkan", true);
 }
 
 std::optional<std::int32_t> render_ahead() {
diff --git a/libs/hwui/api/current.txt b/libs/hwui/api/current.txt
index 7940821..c396a20 100644
--- a/libs/hwui/api/current.txt
+++ b/libs/hwui/api/current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.graphics {
 
   public class ColorMatrix {
diff --git a/libs/hwui/api/module-lib-current.txt b/libs/hwui/api/module-lib-current.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/module-lib-current.txt
+++ b/libs/hwui/api/module-lib-current.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/module-lib-removed.txt b/libs/hwui/api/module-lib-removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/module-lib-removed.txt
+++ b/libs/hwui/api/module-lib-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/removed.txt b/libs/hwui/api/removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/removed.txt
+++ b/libs/hwui/api/removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/system-current.txt b/libs/hwui/api/system-current.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/system-current.txt
+++ b/libs/hwui/api/system-current.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/api/system-removed.txt b/libs/hwui/api/system-removed.txt
index 14191eb..d802177 100644
--- a/libs/hwui/api/system-removed.txt
+++ b/libs/hwui/api/system-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 504dfaa..cedfaed 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -919,6 +919,7 @@
     // buildLayer() will leave the tree in an unknown state, so we must stop drawing
     stopDrawing();
 
+    ScopedActiveContext activeContext(this);
     TreeInfo info(TreeInfo::MODE_FULL, *this);
     info.damageAccumulator = &mDamageAccumulator;
     info.layerUpdateQueue = &mLayerUpdateQueue;
diff --git a/location/Android.bp b/location/Android.bp
index ead46e9..46dca74 100644
--- a/location/Android.bp
+++ b/location/Android.bp
@@ -15,10 +15,27 @@
     ],
 }
 
-java_library {
-    name: "framework-location.stubs.module_lib",
+java_sdk_library {
+    name: "framework-location",
     srcs: [
         ":framework-location-nonupdatable-sources",
     ],
-    sdk_version: "core_platform",
+    defaults: ["framework-non-updatable-unbundled-defaults"],
+    permitted_packages: [
+        "android.location",
+        "com.android.internal.location",
+    ],
+    libs: [
+        "app-compat-annotations",
+        "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+    ],
+    hidden_api_packages: [
+        "com.android.internal.location",
+    ],
+    aidl: {
+        include_dirs: [
+            "frameworks/base/location/java",
+            "frameworks/base/core/java",
+        ],
+    },
 }
diff --git a/location/api/current.txt b/location/api/current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/location/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/location/api/module-lib-current.txt b/location/api/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/location/api/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/location/api/module-lib-removed.txt b/location/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/location/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/location/api/removed.txt b/location/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/location/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/location/api/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/location/api/system-removed.txt b/location/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/location/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/location/api/test-current.txt b/location/api/test-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/location/api/test-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/location/api/test-removed.txt b/location/api/test-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/location/api/test-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/media/Android.bp b/media/Android.bp
index f69dd3c..3493408 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -23,6 +23,10 @@
     name: "soundtrigger_middleware-aidl",
     unstable: true,
     local_include_dir: "aidl",
+    defaults: [
+        "latest_android_media_audio_common_types_import_interface",
+        "latest_android_media_soundtrigger_types_import_interface",
+    ],
     backend: {
         java: {
             sdk_version: "module_current",
@@ -32,8 +36,6 @@
         "aidl/android/media/soundtrigger_middleware/*.aidl",
     ],
     imports: [
-        "android.media.audio.common.types-V2",
-        "android.media.soundtrigger.types-V1",
         "media_permission-aidl",
     ],
 }
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 29e8716..cda919f 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -442,6 +442,7 @@
      * but it must be released if your activity or service is being destroyed.
      */
     public void release() {
+        setCallback(null);
         try {
             mBinder.destroySession();
         } catch (RemoteException e) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index ed1072c..6031ef7 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -81,6 +81,7 @@
         "libhidlallocatorutils",
         "libhidlbase",
         "libsonivox",
+        "server_configurable_flags",
         "android.hardware.cas@1.0",
         "android.hardware.cas.native@1.0",
         "android.hardware.drm@1.3",
@@ -99,6 +100,7 @@
     static_libs: [
         "libgrallocusage",
         "libmedia_midiiowrapper",
+        "android.media.playback.flags-aconfig-cc",
     ],
 
     include_dirs: [
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 1458758..2a10fa7 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -35,7 +35,9 @@
 #include "android_media_MediaDataSource.h"
 #include "android_media_Streams.h"
 #include "android_util_Binder.h"
+#include <com_android_media_playback_flags.h>
 
+namespace playback_flags = com::android::media::playback::flags;
 using namespace android;
 
 struct fields_t {
@@ -374,9 +376,12 @@
         jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
         return NULL;
     }
-    // For getFrameAtTime family of calls, default to ANDROID_BITMAP_FORMAT_RGB_565
-    // to keep the behavior consistent with older releases
-    AndroidBitmapFormat colorFormat = getColorFormat(env, params, ANDROID_BITMAP_FORMAT_RGB_565);
+
+    AndroidBitmapFormat defaultColorFormat =
+            playback_flags::mediametadataretriever_default_rgba8888()
+            ? ANDROID_BITMAP_FORMAT_RGBA_8888
+            : ANDROID_BITMAP_FORMAT_RGB_565;
+    AndroidBitmapFormat colorFormat = getColorFormat(env, params, defaultColorFormat);
 
     // Call native method to retrieve a video frame
     VideoFrame *videoFrame = NULL;
diff --git a/media/jni/playback_flags.aconfig b/media/jni/playback_flags.aconfig
new file mode 100644
index 0000000..2bb0ec5
--- /dev/null
+++ b/media/jni/playback_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.media.playback.flags"
+
+flag {
+  name: "mediametadataretriever_default_rgba8888"
+  namespace: "media_solutions"
+  description: "Change MediaMetadataRetriever to use RGBA8888 for bitmap handling by default."
+  bug: "298965955"
+}
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index 06ec949e..7a329bc 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -20,7 +20,11 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "android-ex-camera2",
+        "android.media.playback.flags-aconfig-java",
+        "flag-junit",
+        "testables",
         "testng",
+        "truth",
     ],
     jni_libs: [
         "libdexmakerjvmtiagent",
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
index f70d2d1..e3d3897 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
@@ -16,19 +16,27 @@
 
 package com.android.mediaframeworktest.unit;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.Bitmap;
 import android.media.MediaMetadataRetriever;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.media.playback.flags.Flags;
 import com.android.mediaframeworktest.MediaNames;
 import com.android.mediaframeworktest.MediaProfileReader;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,6 +48,9 @@
 
     private static final String TAG = "MediaMetadataRetrieverTest";
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     // Test album art extraction.
     @MediumTest
     @Test
@@ -284,6 +295,34 @@
         assertTrue(!hasFailed);
     }
 
+    /** Test the thumbnail is generated when the default is set to RGBA8888 */
+    @MediumTest
+    // TODO(b/305160754) Remove the following annotation and use SetFlagsRule.enableFlags
+    @RequiresFlagsEnabled(Flags.FLAG_MEDIAMETADATARETRIEVER_DEFAULT_RGBA8888)
+    @Test
+    public void testGetFrameAtTimeWithRGBA8888Flag_Set() throws IOException {
+        try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) {
+            retriever.setDataSource(MediaNames.TEST_PATH_1);
+            Bitmap bitmap = retriever.getFrameAtTime(-1);
+            assertNotNull(bitmap);
+            assertEquals(Bitmap.Config.ARGB_8888, bitmap.getConfig());
+        }
+    }
+
+    /** Test the thumbnail is generated when the default is not set to RGBA8888 */
+    @MediumTest
+    // TODO(b/305160754) Remove the following annotation and use SetFlagsRule.disableFlags
+    @RequiresFlagsDisabled(Flags.FLAG_MEDIAMETADATARETRIEVER_DEFAULT_RGBA8888)
+    @Test
+    public void testGetFrameAtTimeWithRGBA8888Flag_Unset() throws IOException {
+        try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) {
+            retriever.setDataSource(MediaNames.TEST_PATH_1);
+            Bitmap bitmap = retriever.getFrameAtTime(-1);
+            assertNotNull(bitmap);
+            assertEquals(Bitmap.Config.RGB_565, bitmap.getConfig());
+        }
+    }
+
     // TODO:
     // Encode and test for the correct mix of metadata elements on a per-file basis?
     // We should be able to compare the actual returned metadata with the expected metadata
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index fe3132e..ceab164 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -21,23 +21,21 @@
 #include <android/font.h>
 #include <android/font_matcher.h>
 #include <android/system_fonts.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <hwui/MinikinSkia.h>
+#include <libxml/parser.h>
+#include <log/log.h>
+#include <minikin/FontCollection.h>
+#include <minikin/LocaleList.h>
+#include <minikin/SystemFonts.h>
+#include <sys/stat.h>
+#include <unistd.h>
 
 #include <memory>
 #include <string>
 #include <vector>
 
-#include <errno.h>
-#include <fcntl.h>
-#include <libxml/tree.h>
-#include <log/log.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <hwui/MinikinSkia.h>
-#include <minikin/FontCollection.h>
-#include <minikin/LocaleList.h>
-#include <minikin/SystemFonts.h>
-
 struct XmlCharDeleter {
     void operator()(xmlChar* b) { xmlFree(b); }
 };
diff --git a/nfc-extras/OWNERS b/nfc-extras/OWNERS
new file mode 100644
index 0000000..35e9713
--- /dev/null
+++ b/nfc-extras/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
index ffed804..fe8386e 100644
--- a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
+++ b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
@@ -16,6 +16,8 @@
 
 package com.android.nfc_extras;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.HashMap;
 
 import android.content.Context;
@@ -30,6 +32,8 @@
  *
  * There is a 1-1 relationship between an {@link NfcAdapterExtras} object and
  * a {@link NfcAdapter} object.
+ *
+ * TODO(b/303286040): Deprecate this API surface since this is no longer supported (see ag/443092)
  */
 public final class NfcAdapterExtras {
     private static final String TAG = "NfcAdapterExtras";
@@ -72,15 +76,40 @@
     private final NfcAdapter mAdapter;
     final String mPackageName;
 
+    private static INfcAdapterExtras
+        getNfcAdapterExtrasInterfaceFromNfcAdapter(NfcAdapter adapter) {
+        try {
+            Method method = NfcAdapter.class.getDeclaredMethod("getNfcAdapterExtrasInterface");
+            method.setAccessible(true);
+            return (INfcAdapterExtras) method.invoke(adapter);
+        } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+                 | IllegalAccessException | IllegalAccessError | InvocationTargetException e) {
+            Log.e(TAG, "Unable to get context from NfcAdapter");
+        }
+        return null;
+    }
+
     /** get service handles */
     private static void initService(NfcAdapter adapter) {
-        final INfcAdapterExtras service = adapter.getNfcAdapterExtrasInterface();
+        final INfcAdapterExtras service = getNfcAdapterExtrasInterfaceFromNfcAdapter(adapter);
         if (service != null) {
             // Leave stale rather than receive a null value.
             sService = service;
         }
     }
 
+    private static Context getContextFromNfcAdapter(NfcAdapter adapter) {
+        try {
+            Method method = NfcAdapter.class.getDeclaredMethod("getContext");
+            method.setAccessible(true);
+            return (Context) method.invoke(adapter);
+        } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+                 | IllegalAccessException | IllegalAccessError | InvocationTargetException e) {
+            Log.e(TAG, "Unable to get context from NfcAdapter");
+        }
+        return null;
+    }
+
     /**
      * Get the {@link NfcAdapterExtras} for the given {@link NfcAdapter}.
      *
@@ -91,7 +120,7 @@
      * @return the {@link NfcAdapterExtras} object for the given {@link NfcAdapter}
      */
     public static NfcAdapterExtras get(NfcAdapter adapter) {
-        Context context = adapter.getContext();
+        Context context = getContextFromNfcAdapter(adapter);
         if (context == null) {
             throw new UnsupportedOperationException(
                     "You must pass a context to your NfcAdapter to use the NFC extras APIs");
@@ -112,7 +141,7 @@
 
     private NfcAdapterExtras(NfcAdapter adapter) {
         mAdapter = adapter;
-        mPackageName = adapter.getContext().getPackageName();
+        mPackageName = getContextFromNfcAdapter(adapter).getPackageName();
         mEmbeddedEe = new NfcExecutionEnvironment(this);
         mRouteOnWhenScreenOn = new CardEmulationRoute(CardEmulationRoute.ROUTE_ON_WHEN_SCREEN_ON,
                 mEmbeddedEe);
@@ -156,12 +185,24 @@
         }
     }
 
+    private static void attemptDeadServiceRecoveryOnNfcAdapter(NfcAdapter adapter, Exception e) {
+        try {
+            Method method = NfcAdapter.class.getDeclaredMethod(
+                    "attemptDeadServiceRecovery", Exception.class);
+            method.setAccessible(true);
+            method.invoke(adapter, e);
+        } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+                 | IllegalAccessException | IllegalAccessError | InvocationTargetException ex) {
+            Log.e(TAG, "Unable to attempt dead service recovery on NfcAdapter");
+        }
+    }
+
     /**
      * NFC service dead - attempt best effort recovery
      */
     void attemptDeadServiceRecovery(Exception e) {
         Log.e(TAG, "NFC Adapter Extras dead - attempting to recover");
-        mAdapter.attemptDeadServiceRecovery(e);
+        attemptDeadServiceRecoveryOnNfcAdapter(mAdapter, e);
         initService(mAdapter);
     }
 
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
index 58bcd1d..e71597a 100644
--- a/omapi/aidl/Android.bp
+++ b/omapi/aidl/Android.bp
@@ -24,6 +24,11 @@
     backend: {
         java: {
             sdk_version: "module_current",
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.nfcservices",
+            ],
+
         },
         rust: {
             enabled: true,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
index 3505cfb..74f04e0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
@@ -33,8 +33,6 @@
 
 import androidx.annotation.Nullable;
 
-import java.io.File;
-
 /**
  * Installation failed: Return status code to the caller or display failure UI to user
  */
@@ -101,14 +99,8 @@
             // Set header icon and title
             PackageUtil.AppSnippet as;
             PackageManager pm = getPackageManager();
-
-            if ("package".equals(packageURI.getScheme())) {
-                as = new PackageUtil.AppSnippet(pm.getApplicationLabel(appInfo),
-                        pm.getApplicationIcon(appInfo));
-            } else {
-                final File sourceFile = new File(packageURI.getPath());
-                as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
-            }
+            as = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET,
+                    PackageUtil.AppSnippet.class);
 
             // Store label for dialog
             mLabel = as.label;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 7bea339..1088ace 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -16,6 +16,7 @@
 
 package com.android.packageinstaller;
 
+import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_APP_SNIPPET;
 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
 
 import android.app.PendingIntent;
@@ -86,7 +87,8 @@
             // ContentResolver.SCHEME_FILE
             // STAGED_SESSION_ID extra contains an ID of a previously staged install session.
             final File sourceFile = new File(mPackageURI.getPath());
-            PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
+            PackageUtil.AppSnippet as = getIntent()
+                    .getParcelableExtra(EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class);
 
             mAlert.setIcon(as.icon);
             mAlert.setTitle(as.label);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 73c03a5..579fb7b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -23,7 +23,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.net.Uri;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
@@ -31,7 +30,6 @@
 
 import androidx.annotation.Nullable;
 
-import java.io.File;
 import java.util.List;
 
 /**
@@ -66,18 +64,8 @@
             ApplicationInfo appInfo =
                     intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
             mAppPackageName = appInfo.packageName;
-            Uri packageURI = intent.getData();
-
-            // Set header icon and title
-            PackageManager pm = getPackageManager();
-
-            if ("package".equals(packageURI.getScheme())) {
-                mAppSnippet = new PackageUtil.AppSnippet(pm.getApplicationLabel(appInfo),
-                        pm.getApplicationIcon(appInfo));
-            } else {
-                File sourceFile = new File(packageURI.getPath());
-                mAppSnippet = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
-            }
+            mAppSnippet = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET,
+                    PackageUtil.AppSnippet.class);
 
             mLaunchIntent = getPackageManager().getLaunchIntentForPackage(mAppPackageName);
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 38d5d3a..1e20168 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -81,6 +81,7 @@
     static final String EXTRA_CALLING_ATTRIBUTION_TAG = "EXTRA_CALLING_ATTRIBUTION_TAG";
     static final String EXTRA_ORIGINAL_SOURCE_INFO = "EXTRA_ORIGINAL_SOURCE_INFO";
     static final String EXTRA_STAGED_SESSION_ID = "EXTRA_STAGED_SESSION_ID";
+    static final String EXTRA_APP_SNIPPET = "EXTRA_APP_SNIPPET";
     private static final String ALLOW_UNKNOWN_SOURCES_KEY =
             PackageInstallerActivity.class.getName() + "ALLOW_UNKNOWN_SOURCES_KEY";
 
@@ -595,7 +596,7 @@
                 CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
                 if (mLocalLOGV) Log.i(TAG, "creating snippet for " + label);
                 mAppSnippet = new PackageUtil.AppSnippet(label,
-                        mPm.getApplicationIcon(mPkgInfo.applicationInfo));
+                        mPm.getApplicationIcon(mPkgInfo.applicationInfo), getBaseContext());
             } break;
 
             case ContentResolver.SCHEME_FILE: {
@@ -633,7 +634,7 @@
         mPkgInfo = generateStubPackageInfo(info.getAppPackageName());
         mAppSnippet = new PackageUtil.AppSnippet(info.getAppLabel(),
                 info.getAppIcon() != null ? new BitmapDrawable(getResources(), info.getAppIcon())
-                        : getPackageManager().getDefaultActivityIcon());
+                        : getPackageManager().getDefaultActivityIcon(), getBaseContext());
         return true;
     }
 
@@ -693,6 +694,9 @@
         if (stagedSessionId > 0) {
             newIntent.putExtra(EXTRA_STAGED_SESSION_ID, stagedSessionId);
         }
+        if (mAppSnippet != null) {
+            newIntent.putExtra(EXTRA_APP_SNIPPET, mAppSnippet);
+        }
         newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
         if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI);
         startActivity(newIntent);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index ff0e5fb..f6f7acc 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -18,6 +18,7 @@
 package com.android.packageinstaller;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -27,8 +28,13 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
@@ -115,18 +121,75 @@
                 icon);
     }
 
-    static final class AppSnippet {
+    static final class AppSnippet implements Parcelable {
         @NonNull public CharSequence label;
         @Nullable public Drawable icon;
-        public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) {
+        public int iconSize;
+
+        public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon, Context context) {
             this.label = label;
             this.icon = icon;
+            final ActivityManager am = context.getSystemService(ActivityManager.class);
+            this.iconSize = am.getLauncherLargeIconSize();
+        }
+
+        private AppSnippet(Parcel in) {
+            label = in.readString();
+            Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class);
+            icon = new BitmapDrawable(Resources.getSystem(), bmp);
+            iconSize = in.readInt();
         }
 
         @Override
         public String toString() {
             return "AppSnippet[" + label + (icon != null ? "(has" : "(no ") + " icon)]";
         }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString(label.toString());
+            Bitmap bmp = getBitmapFromDrawable(icon);
+            dest.writeParcelable(bmp, 0);
+            dest.writeInt(iconSize);
+        }
+
+        private Bitmap getBitmapFromDrawable(Drawable drawable) {
+            // Create an empty bitmap with the dimensions of our drawable
+            final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                    drawable.getIntrinsicHeight(),
+                    Bitmap.Config.ARGB_8888);
+            // Associate it with a canvas. This canvas will draw the icon on the bitmap
+            final Canvas canvas = new Canvas(bmp);
+            // Draw the drawable in the canvas. The canvas will ultimately paint the drawable in the
+            // bitmap held within
+            drawable.draw(canvas);
+
+            // Scale it down if the icon is too large
+            if ((bmp.getWidth() > iconSize * 2) || (bmp.getHeight() > iconSize * 2)) {
+                Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true);
+                if (scaledBitmap != bmp) {
+                    bmp.recycle();
+                }
+                return scaledBitmap;
+            }
+
+            return bmp;
+        }
+
+        public static final Parcelable.Creator<AppSnippet> CREATOR = new Parcelable.Creator<>() {
+            public AppSnippet createFromParcel(Parcel in) {
+                return new AppSnippet(in);
+            }
+
+            public AppSnippet[] newArray(int size) {
+                return new AppSnippet[size];
+            }
+        };
     }
 
     /**
@@ -171,7 +234,7 @@
         } catch (OutOfMemoryError e) {
             Log.i(LOG_TAG, "Could not load app icon", e);
         }
-        return new PackageUtil.AppSnippet(label, icon);
+        return new PackageUtil.AppSnippet(label, icon, pContext);
     }
 
     /**
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 873b434..618f7ed 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -590,6 +590,7 @@
                     Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
                     Settings.Global.AUTO_REVOKE_PARAMETERS,
                     Settings.Global.ENABLE_RADIO_BUG_DETECTION,
+                    Settings.Global.REPAIR_MODE_ACTIVE,
                     Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
                     Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD,
                     Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT,
@@ -711,6 +712,7 @@
                  Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
                  Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
                  Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY,
+                 Settings.Secure.AUDIO_DEVICE_INVENTORY, // setting not controllable by user
                  Settings.Secure.BACKUP_AUTO_RESTORE,
                  Settings.Secure.BACKUP_ENABLED,
                  Settings.Secure.BACKUP_PROVISIONED,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ee9883b..1edb89c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -588,6 +588,9 @@
     <!-- Permission needed for CTS test - ConcurrencyTest#testP2pSetWfdInfo -->
     <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
 
+    <!-- Permission required for CTS test - CtsThreadNetworkTestCases -->
+    <uses-permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED"/>
+
     <!-- Permission required for CTS tests to enable/disable rate limiting toasts. -->
     <uses-permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" />
 
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 8f329b3..967a36b 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -27,6 +27,8 @@
 chandruis@google.com
 chrisgollner@google.com
 cinek@google.com
+cocod@google.com
+darrellshi@google.com
 dupin@google.com
 ethibodeau@google.com
 evanlaird@google.com
@@ -36,6 +38,7 @@
 hwwang@google.com
 hyunyoungs@google.com
 ikateryna@google.com
+iyz@google.com
 jaggies@google.com
 jamesoleary@google.com
 jbolinger@google.com
@@ -54,10 +57,12 @@
 kozynski@google.com
 kprevas@google.com
 lusilva@google.com
+liuyining@google.com
 lynhan@google.com
 madym@google.com
 mankoff@google.com
 mateuszc@google.com
+matiashe@google.com
 mgalhardo@google.com
 michaelmikhil@google.com
 michschn@google.com
@@ -77,6 +82,7 @@
 pinyaoting@google.com
 pixel@google.com
 pomini@google.com
+princedonkor@google.com
 rahulbanerjee@google.com
 roosa@google.com
 saff@google.com
@@ -94,10 +100,12 @@
 tsuji@google.com
 twickham@google.com
 vadimt@google.com
+valiiftime@google.com
 vanjan@google.com
 victortulias@google.com
 winsonc@google.com
 wleshner@google.com
+wxyz@google.com
 xilei@google.com
 xuqiu@google.com
 yeinj@google.com
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index d9fe949..b9ee63a 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -62,7 +62,8 @@
     <com.android.systemui.statusbar.LightRevealScrim
         android:id="@+id/light_reveal_scrim"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="match_parent"
+        sysui:ignoreRightInset="true" />
 
     <include layout="@layout/status_bar_expanded"
              android:layout_width="match_parent"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/OWNERS
new file mode 100644
index 0000000..7f5384d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
\ No newline at end of file
diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java
index 1866a99..67c2caa 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -16,9 +16,12 @@
 
 package android.renderscript;
 
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.res.Resources;
+import android.util.Slog;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -35,6 +38,15 @@
     private static final String TAG = "ScriptC";
 
     /**
+     * In targetSdkVersion 35 and above, Renderscript's ScriptC stops being supported
+     * and an exception is thrown when the class is instantiated.
+     * In targetSdkVersion 34 and below, Renderscript's ScriptC still works.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = 35)
+    private static final long RENDERSCRIPT_SCRIPTC_DEPRECATION_CHANGE_ID = 297019750L;
+
+    /**
      * Only intended for use by the generated derived classes.
      *
      * @param id
@@ -89,7 +101,19 @@
         setID(id);
     }
 
+    private static void throwExceptionIfSDKTooHigh() {
+        String message =
+                "ScriptC scripts are not supported when targeting an API Level >= 35. Please refer "
+                    + "to https://developer.android.com/guide/topics/renderscript/migration-guide "
+                    + "for proposed alternatives.";
+        Slog.w(TAG, message);
+        if (CompatChanges.isChangeEnabled(RENDERSCRIPT_SCRIPTC_DEPRECATION_CHANGE_ID)) {
+            throw new UnsupportedOperationException(message);
+        }
+    }
+
     private static synchronized long internalCreate(RenderScript rs, Resources resources, int resourceID) {
+        throwExceptionIfSDKTooHigh();
         byte[] pgm;
         int pgmLength;
         InputStream is = resources.openRawResource(resourceID);
@@ -126,6 +150,7 @@
 
     private static synchronized long internalStringCreate(RenderScript rs, String resName, byte[] bitcode) {
         //        Log.v(TAG, "Create script for resource = " + resName);
+        throwExceptionIfSDKTooHigh();
         return rs.nScriptCCreate(resName, RenderScript.getCachePath(), bitcode, bitcode.length);
     }
 }
diff --git a/services/Android.bp b/services/Android.bp
index 66181f5..0f6b984 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -58,6 +58,7 @@
                 optimize: false,
                 shrink: true,
                 ignore_warnings: false,
+                proguard_compatibility: false,
                 proguard_flags_files: [
                     "proguard.flags",
                     // Ensure classes referenced in the framework-res manifest
@@ -235,12 +236,14 @@
 stubs_defaults {
     name: "services-stubs-default",
     installable: false,
-    args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)" +
-        " --hide-annotation android.annotation.Hide" +
-        " --hide InternalClasses" + // com.android.* classes are okay in this interface
+    flags: [
+        "--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)",
+        "--hide-annotation android.annotation.Hide",
+        "--hide InternalClasses", // com.android.* classes are okay in this interface
         // TODO: remove the --hide options below
-        " --hide DeprecationMismatch" +
-        " --hide HiddenTypedefConstant",
+        "--hide DeprecationMismatch",
+        "--hide HiddenTypedefConstant",
+    ],
     visibility: ["//frameworks/base:__subpackages__"],
     filter_packages: ["com.android."],
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS
index 83143a4..5295ec8 100644
--- a/services/companion/java/com/android/server/companion/virtual/OWNERS
+++ b/services/companion/java/com/android/server/companion/virtual/OWNERS
@@ -1,3 +1,5 @@
+# Bug component: 1171888
+
 set noparent
 
 ogunwale@google.com
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9ac30f3..22f8570 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -130,6 +130,7 @@
         "android.hardware.light-V2.0-java",
         "android.hardware.gnss-V2-java",
         "android.hardware.vibrator-V2-java",
+        "android.nfc.flags-aconfig-java",
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
         "service-art.stubs.system_server",
diff --git a/services/core/java/com/android/server/BrickReceiver.java b/services/core/java/com/android/server/BrickReceiver.java
deleted file mode 100644
index cff3805..0000000
--- a/services/core/java/com/android/server/BrickReceiver.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2008 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;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.BroadcastReceiver;
-import android.os.SystemService;
-import android.util.Slog;
-
-public class BrickReceiver extends BroadcastReceiver {
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Slog.w("BrickReceiver", "!!! BRICKING DEVICE !!!");
-        SystemService.start("brick");
-    }
-}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index b67e627..e92f043 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -179,6 +179,8 @@
 3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(short_lived|1|1),(max_build_time|1|3),(cumm_build_time|2|3)
 # Snapshot rebuild instance
 3131 pm_snapshot_rebuild (build_time|1|3),(lifetime|1|3)
+# Caller information to clear application data
+3132 pm_clear_app_data_caller (pid|1),(uid|1),(package|3)
 
 # ---------------------------
 # InputMethodManagerService.java
@@ -218,6 +220,14 @@
 35000 auto_brightness_adj (old_lux|5),(old_brightness|5),(new_lux|5),(new_brightness|5)
 
 # ---------------------------
+# Installer.java
+# ---------------------------
+# Caller Information to clear application data
+39000 installer_clear_app_data_caller (pid|1),(uid|1),(package|3),(flags|1)
+# Call stack to clear application data
+39001 installer_clear_app_data_call_stack (method|3),(class|3),(file|3),(line|1)
+
+# ---------------------------
 # ConnectivityService.java
 # ---------------------------
 # Connectivity state changed
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7142828..61584b7 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -377,15 +377,14 @@
     private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_STORAGE);
 
     /**
-     * mLocalUnlockedUsers affects the return value of isUserUnlocked.  If
-     * any value in the array changes, then the binder cache for
-     * isUserUnlocked must be invalidated.  When adding mutating methods to
-     * WatchedLockedUsers, be sure to invalidate the cache in the new
-     * methods.
+     * mCeUnlockedUsers affects the return value of {@link UserManager#isUserUnlocked}.  If any
+     * value in the array changes, then the binder cache for {@link UserManager#isUserUnlocked} must
+     * be invalidated.  When adding mutating methods to this class, be sure to invalidate the cache
+     * in the new methods.
      */
-    private static class WatchedLockedUsers {
+    private static class WatchedUnlockedUsers {
         private int[] users = EmptyArray.INT;
-        public WatchedLockedUsers() {
+        public WatchedUnlockedUsers() {
             invalidateIsUserUnlockedCache();
         }
         public void append(int userId) {
@@ -417,10 +416,14 @@
         }
     }
 
-    /** Set of users that we know are unlocked. */
+    /** Set of users whose CE storage is unlocked. */
     @GuardedBy("mLock")
-    private WatchedLockedUsers mLocalUnlockedUsers = new WatchedLockedUsers();
-    /** Set of users that system knows are unlocked. */
+    private WatchedUnlockedUsers mCeUnlockedUsers = new WatchedUnlockedUsers();
+
+    /**
+     * Set of users that are in the RUNNING_UNLOCKED state.  This differs from {@link
+     * mCeUnlockedUsers} in that a user can be stopped but still have its CE storage unlocked.
+     */
     @GuardedBy("mLock")
     private int[] mSystemUnlockedUsers = EmptyArray.INT;
 
@@ -1135,11 +1138,10 @@
         }
     }
 
-    // If vold knows that some users have their storage unlocked already (which
-    // can happen after a "userspace reboot"), then add those users to
-    // mLocalUnlockedUsers.  Do this right away and don't wait until
-    // PHASE_BOOT_COMPLETED, since the system may unlock users before then.
-    private void restoreLocalUnlockedUsers() {
+    // If vold knows that some users have their CE storage unlocked already (which can happen after
+    // a "userspace reboot"), then add those users to mCeUnlockedUsers.  Do this right away and
+    // don't wait until PHASE_BOOT_COMPLETED, since the system may unlock users before then.
+    private void restoreCeUnlockedUsers() {
         final int[] userIds;
         try {
             userIds = mVold.getUnlockedUsers();
@@ -1155,7 +1157,7 @@
                 // reconnecting to vold after it crashed and was restarted, in
                 // which case things will be the other way around --- we'll know
                 // about the unlocked users but vold won't.
-                mLocalUnlockedUsers.appendAll(userIds);
+                mCeUnlockedUsers.appendAll(userIds);
             }
         }
     }
@@ -2062,7 +2064,7 @@
                 connectVold();
             }, DateUtils.SECOND_IN_MILLIS);
         } else {
-            restoreLocalUnlockedUsers();
+            restoreCeUnlockedUsers();
             onDaemonConnected();
         }
     }
@@ -2982,7 +2984,7 @@
             // We need all the users unlocked to move their primary storage
             users = mContext.getSystemService(UserManager.class).getUsers();
             for (UserInfo user : users) {
-                if (StorageManager.isFileEncrypted() && !isUserKeyUnlocked(user.id)) {
+                if (StorageManager.isFileEncrypted() && !isCeStorageUnlocked(user.id)) {
                     Slog.w(TAG, "Failing move due to locked user " + user.id);
                     onMoveStatusLocked(PackageManager.MOVE_FAILED_LOCKED_USER);
                     return;
@@ -3206,15 +3208,15 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
+    public void createUserStorageKeys(int userId, int serialNumber, boolean ephemeral) {
 
-        super.createUserKey_enforcePermission();
+        super.createUserStorageKeys_enforcePermission();
 
         try {
-            mVold.createUserKey(userId, serialNumber, ephemeral);
-            // New keys are always unlocked.
+            mVold.createUserStorageKeys(userId, serialNumber, ephemeral);
+            // Since the user's CE key was just created, the user's CE storage is now unlocked.
             synchronized (mLock) {
-                mLocalUnlockedUsers.append(userId);
+                mCeUnlockedUsers.append(userId);
             }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
@@ -3223,15 +3225,15 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void destroyUserKey(int userId) {
+    public void destroyUserStorageKeys(int userId) {
 
-        super.destroyUserKey_enforcePermission();
+        super.destroyUserStorageKeys_enforcePermission();
 
         try {
-            mVold.destroyUserKey(userId);
-            // Destroying a key also locks it.
+            mVold.destroyUserStorageKeys(userId);
+            // Since the user's CE key was just destroyed, the user's CE storage is now locked.
             synchronized (mLock) {
-                mLocalUnlockedUsers.remove(userId);
+                mCeUnlockedUsers.remove(userId);
             }
         } catch (Exception e) {
             Slog.wtf(TAG, e);
@@ -3241,60 +3243,60 @@
     /* Only for use by LockSettingsService */
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void setUserKeyProtection(@UserIdInt int userId, byte[] secret) throws RemoteException {
-        super.setUserKeyProtection_enforcePermission();
+    public void setCeStorageProtection(@UserIdInt int userId, byte[] secret)
+            throws RemoteException {
+        super.setCeStorageProtection_enforcePermission();
 
-        mVold.setUserKeyProtection(userId, HexDump.toHexString(secret));
+        mVold.setCeStorageProtection(userId, HexDump.toHexString(secret));
     }
 
     /* Only for use by LockSettingsService */
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void unlockUserKey(@UserIdInt int userId, int serialNumber, byte[] secret)
-        throws RemoteException {
-        super.unlockUserKey_enforcePermission();
+    public void unlockCeStorage(@UserIdInt int userId, int serialNumber, byte[] secret)
+            throws RemoteException {
+        super.unlockCeStorage_enforcePermission();
 
         if (StorageManager.isFileEncrypted()) {
-            mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret));
+            mVold.unlockCeStorage(userId, serialNumber, HexDump.toHexString(secret));
         }
         synchronized (mLock) {
-            mLocalUnlockedUsers.append(userId);
+            mCeUnlockedUsers.append(userId);
         }
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL)
     @Override
-    public void lockUserKey(int userId) {
-        //  Do not lock user 0 data for headless system user
-        super.lockUserKey_enforcePermission();
+    public void lockCeStorage(int userId) {
+        super.lockCeStorage_enforcePermission();
 
+        // Never lock the CE storage of a headless system user.
         if (userId == UserHandle.USER_SYSTEM
                 && UserManager.isHeadlessSystemUserMode()) {
             throw new IllegalArgumentException("Headless system user data cannot be locked..");
         }
 
-
-        if (!isUserKeyUnlocked(userId)) {
+        if (!isCeStorageUnlocked(userId)) {
             Slog.d(TAG, "User " + userId + "'s CE storage is already locked");
             return;
         }
 
         try {
-            mVold.lockUserKey(userId);
+            mVold.lockCeStorage(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
             return;
         }
 
         synchronized (mLock) {
-            mLocalUnlockedUsers.remove(userId);
+            mCeUnlockedUsers.remove(userId);
         }
     }
 
     @Override
-    public boolean isUserKeyUnlocked(int userId) {
+    public boolean isCeStorageUnlocked(int userId) {
         synchronized (mLock) {
-            return mLocalUnlockedUsers.contains(userId);
+            return mCeUnlockedUsers.contains(userId);
         }
     }
 
@@ -3679,8 +3681,8 @@
         final int userId = UserHandle.getUserId(callingUid);
         final String propertyName = "sys.user." + userId + ".ce_available";
 
-        // Ignore requests to create directories while storage is locked
-        if (!isUserKeyUnlocked(userId)) {
+        // Ignore requests to create directories while CE storage is locked
+        if (!isCeStorageUnlocked(userId)) {
             throw new IllegalStateException("Failed to prepare " + appPath);
         }
 
@@ -3806,15 +3808,15 @@
         final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM);
 
         final boolean userIsDemo;
-        final boolean userKeyUnlocked;
         final boolean storagePermission;
+        final boolean ceStorageUnlocked;
         final long token = Binder.clearCallingIdentity();
         try {
             userIsDemo = LocalServices.getService(UserManagerInternal.class)
                     .getUserInfo(userId).isDemo();
             storagePermission = mStorageManagerInternal.hasExternalStorage(callingUid,
                     callingPackage);
-            userKeyUnlocked = isUserKeyUnlocked(userId);
+            ceStorageUnlocked = isCeStorageUnlocked(userId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -3874,7 +3876,7 @@
                 } else if (!systemUserUnlocked) {
                     reportUnmounted = true;
                     Slog.w(TAG, "Reporting " + volId + " unmounted due to system locked");
-                } else if ((vol.getType() == VolumeInfo.TYPE_EMULATED) && !userKeyUnlocked) {
+                } else if ((vol.getType() == VolumeInfo.TYPE_EMULATED) && !ceStorageUnlocked) {
                     reportUnmounted = true;
                     Slog.w(TAG, "Reporting " + volId + "unmounted due to " + userId + " locked");
                 } else if (!storagePermission && !realState) {
@@ -4678,7 +4680,7 @@
             }
 
             pw.println();
-            pw.println("Local unlocked users: " + mLocalUnlockedUsers);
+            pw.println("CE unlocked users: " + mCeUnlockedUsers);
             pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers));
         }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e6df1d9..c0da30d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2249,7 +2249,7 @@
             return;
         }
         // TODO(b/148767783): should we check all profiles under user0?
-        UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+        UserspaceRebootLogger.logEventAsync(StorageManager.isCeStorageUnlocked(userId),
                 BackgroundThread.getExecutor());
     }
 
@@ -3468,6 +3468,7 @@
         enforceNotIsolatedCaller("clearApplicationUserData");
         int uid = Binder.getCallingUid();
         int pid = Binder.getCallingPid();
+        EventLog.writeEvent(EventLogTags.AM_CLEAR_APP_DATA_CALLER, pid, uid, packageName);
         final int resolvedUserId = mUserController.handleIncomingUser(pid, uid, userId, false,
                 ALLOW_FULL_ONLY, "clearApplicationUserData", null);
 
@@ -4564,7 +4565,7 @@
             // We carefully use the same state that PackageManager uses for
             // filtering, since we use this flag to decide if we need to install
             // providers when user is unlocked later
-            app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
+            app.setUnlocked(StorageManager.isCeStorageUnlocked(app.userId));
         }
 
         boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 2e45da3..d56448d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1174,7 +1174,7 @@
                 synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) {
                     app.mOptRecord.setFreezeSticky(isSticky);
                     mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0,
-                            false);
+                            true);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 9e9db6a..2aed847 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -129,3 +129,6 @@
 
 # Intent Sender redirect for UserHandle.USER_CURRENT
 30110 am_intent_sender_redirect_user (userId|1|5)
+
+# Caller information to clear application data
+30120 am_clear_app_data_caller (pid|1),(uid|1),(package|3)
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 4380b42..507fd9e 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -44,6 +44,7 @@
     private static final int DIRECT_RECL_AND_THRASHING = 5;
     private static final int LOW_MEM_AND_SWAP_UTIL = 6;
     private static final int LOW_FILECACHE_AFTER_THRASHING = 7;
+    private static final int LOW_MEM = 8;
 
     /**
      * Processes the LMK_KILL_OCCURRED packet data
@@ -106,6 +107,8 @@
                 return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__LOW_MEM_AND_SWAP_UTIL;
             case LOW_FILECACHE_AFTER_THRASHING:
                 return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__LOW_FILECACHE_AFTER_THRASHING;
+            case LOW_MEM:
+                return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__LOW_MEM;
             default:
                 return FrameworkStatsLog.LMK_KILL_OCCURRED__REASON__UNKNOWN;
         }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f21ad22..579d35c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2712,8 +2712,7 @@
             }
         }
 
-        if (ppr.getLastProviderTime() > 0
-                && (ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
+        if ((ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
             if (adj > PREVIOUS_APP_ADJ) {
                 adj = PREVIOUS_APP_ADJ;
                 schedGroup = SCHED_GROUP_BACKGROUND;
diff --git a/services/core/java/com/android/server/am/ProcessProviderRecord.java b/services/core/java/com/android/server/am/ProcessProviderRecord.java
index 751e8a82..9b72a3a 100644
--- a/services/core/java/com/android/server/am/ProcessProviderRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProviderRecord.java
@@ -34,7 +34,7 @@
     /**
      * The last time someone else was using a provider in this process.
      */
-    private long mLastProviderTime;
+    private long mLastProviderTime = Long.MIN_VALUE;
 
     /**
      * class (String) -> ContentProviderRecord.
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index db341d2..265ab04 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -280,7 +280,7 @@
      * The last time the process was in the TOP state or greater.
      */
     @GuardedBy("mService")
-    private long mLastTopTime;
+    private long mLastTopTime = Long.MIN_VALUE;
 
     /**
      * Is this an empty background process?
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 76a994e..6eb2961 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -639,8 +639,8 @@
         mInjector.getUserJourneyLogger()
                 .logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_UNLOCKING_USER,
                 EVENT_STATE_BEGIN);
-        // If the user key hasn't been unlocked yet, we cannot proceed.
-        if (!StorageManager.isUserKeyUnlocked(userId)) return false;
+        // If the user's CE storage hasn't been unlocked yet, we cannot proceed.
+        if (!StorageManager.isCeStorageUnlocked(userId)) return false;
         synchronized (mLock) {
             // Do not proceed if unexpected state or a stale user
             if (mStartedUsers.get(userId) != uss || uss.state != STATE_RUNNING_LOCKED) {
@@ -655,8 +655,8 @@
 
         // Call onBeforeUnlockUser on a worker thread that allows disk I/O
         FgThread.getHandler().post(() -> {
-            if (!StorageManager.isUserKeyUnlocked(userId)) {
-                Slogf.w(TAG, "User key got locked unexpectedly, leaving user locked.");
+            if (!StorageManager.isCeStorageUnlocked(userId)) {
+                Slogf.w(TAG, "User's CE storage got locked unexpectedly, leaving user locked.");
                 return;
             }
 
@@ -690,8 +690,8 @@
     private void finishUserUnlocked(final UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
         EventLog.writeEvent(EventLogTags.UC_FINISH_USER_UNLOCKED, userId);
-        // Only keep marching forward if user is actually unlocked
-        if (!StorageManager.isUserKeyUnlocked(userId)) return;
+        // Only keep marching forward if the user's CE storage is unlocked.
+        if (!StorageManager.isCeStorageUnlocked(userId)) return;
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
@@ -777,8 +777,8 @@
         if (userInfo == null) {
             return;
         }
-        // Only keep marching forward if user is actually unlocked
-        if (!StorageManager.isUserKeyUnlocked(userId)) return;
+        // Only keep marching forward if the user's CE storage is unlocked.
+        if (!StorageManager.isCeStorageUnlocked(userId)) return;
 
         // Remember that we logged in
         mInjector.getUserManager().onUserLoggedIn(userId);
@@ -1256,7 +1256,7 @@
             }
             try {
                 Slogf.i(TAG, "Locking CE storage for user #" + userId);
-                mInjector.getStorageManager().lockUserKey(userId);
+                mInjector.getStorageManager().lockCeStorage(userId);
             } catch (RemoteException re) {
                 throw re.rethrowAsRuntimeException();
             }
@@ -1874,8 +1874,8 @@
         }
 
         UserState uss;
-        if (!StorageManager.isUserKeyUnlocked(userId)) {
-            // We always want to try to unlock the user key, even if the user is not started yet.
+        if (!StorageManager.isCeStorageUnlocked(userId)) {
+            // We always want to try to unlock CE storage, even if the user is not started yet.
             mLockPatternUtils.unlockUserKeyIfUnsecured(userId);
         }
         synchronized (mLock) {
@@ -2662,10 +2662,10 @@
                 case UserState.STATE_RUNNING_UNLOCKING:
                 case UserState.STATE_RUNNING_UNLOCKED:
                     return true;
-                // In the stopping/shutdown state return unlock state of the user key
+                // In the stopping/shutdown state, return unlock state of the user's CE storage.
                 case UserState.STATE_STOPPING:
                 case UserState.STATE_SHUTDOWN:
-                    return StorageManager.isUserKeyUnlocked(userId);
+                    return StorageManager.isCeStorageUnlocked(userId);
                 default:
                     return false;
             }
@@ -2674,10 +2674,10 @@
             switch (state.state) {
                 case UserState.STATE_RUNNING_UNLOCKED:
                     return true;
-                // In the stopping/shutdown state return unlock state of the user key
+                // In the stopping/shutdown state, return unlock state of the user's CE storage.
                 case UserState.STATE_STOPPING:
                 case UserState.STATE_SHUTDOWN:
-                    return StorageManager.isUserKeyUnlocked(userId);
+                    return StorageManager.isCeStorageUnlocked(userId);
                 default:
                     return false;
             }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index e4a5a3e..2358436 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -399,59 +399,6 @@
         }
     }
 
-    public enum FrameRate {
-        FPS_DEFAULT(0),
-        FPS_30(30),
-        FPS_36(36),
-        FPS_40(40),
-        FPS_45(45),
-        FPS_48(48),
-        FPS_60(60),
-        FPS_72(72),
-        FPS_90(90),
-        FPS_120(120),
-        FPS_144(144),
-        FPS_INVALID(-1);
-
-        public final int fps;
-
-        FrameRate(int fps) {
-            this.fps = fps;
-        }
-    }
-
-    // Turn the raw string to the corresponding fps int.
-    // Return 0 when disabling, -1 for invalid fps.
-    static int getFpsInt(String raw) {
-        // TODO(b/243448953): make sure this translates to proper values based on current display
-        switch (raw) {
-            case "30":
-                return FrameRate.FPS_30.fps;
-            case "36":
-                return FrameRate.FPS_36.fps;
-            case "40":
-                return FrameRate.FPS_40.fps;
-            case "45":
-                return FrameRate.FPS_45.fps;
-            case "48":
-                return FrameRate.FPS_48.fps;
-            case "60":
-                return FrameRate.FPS_60.fps;
-            case "72":
-                return FrameRate.FPS_72.fps;
-            case "90":
-                return FrameRate.FPS_90.fps;
-            case "120":
-                return FrameRate.FPS_120.fps;
-            case "144":
-                return FrameRate.FPS_144.fps;
-            case "disable":
-            case "":
-                return FrameRate.FPS_DEFAULT.fps;
-        }
-        return FrameRate.FPS_INVALID.fps;
-    }
-
     /**
      * Called by games to communicate the current state to the platform.
      *
@@ -699,7 +646,12 @@
             }
 
             public synchronized int getFps() {
-                return GameManagerService.getFpsInt(mFps);
+                try {
+                    final int fpsInt = Integer.parseInt(mFps);
+                    return fpsInt;
+                } catch (NumberFormatException e) {
+                    return 0;
+                }
             }
 
             synchronized String getFpsStr() {
@@ -739,7 +691,12 @@
             }
 
             android.app.GameModeConfiguration toPublicGameModeConfig() {
-                int fpsOverride = getFpsInt(mFps);
+                int fpsOverride;
+                try {
+                    fpsOverride = Integer.parseInt(mFps);
+                } catch (NumberFormatException e) {
+                    fpsOverride = 0;
+                }
                 // TODO(b/243448953): match to proper value in case of display change?
                 fpsOverride = fpsOverride > 0 ? fpsOverride
                         : android.app.GameModeConfiguration.FPS_OVERRIDE_NONE;
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index 00ff489..ab57c4f 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -241,8 +241,10 @@
                 case "--fps":
                     if (fpsStr == null) {
                         fpsStr = getNextArgRequired();
-                        if (fpsStr != null && GameManagerService.getFpsInt(fpsStr) == -1) {
-                            pw.println("Invalid frame rate '" + fpsStr + "'");
+                        try {
+                            Integer.parseInt(fpsStr);
+                        } catch (NumberFormatException e) {
+                            pw.println("Invalid frame rate: '" + fpsStr + "'");
                             return -1;
                         }
                     } else {
@@ -375,8 +377,8 @@
         pw.println("      --downscale [0.3|0.35|0.4|0.45|0.5|0.55|0.6|0.65");
         pw.println("                  |0.7|0.75|0.8|0.85|0.9|disable]: Set app to run at the");
         pw.println("                                                   specified scaling ratio.");
-        pw.println("      --fps [30|45|60|90|120|disable]: Set app to run at the specified fps,");
-        pw.println("                                       if supported.");
+        pw.println("      --fps: Integer value to set app to run at the specified fps,");
+        pw.println("             if supported. 0 to disable.");
         pw.println("  reset [--mode [2|3|performance|battery] --user <USER_ID>] <PACKAGE_NAME>");
         pw.println("      Resets the game mode of the app to device configuration.");
         pw.println("      This should only be used to reset any override to non custom game mode");
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
new file mode 100644
index 0000000..a466235
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -0,0 +1,204 @@
+/*
+ * 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.audio;
+
+import static android.media.AudioSystem.DEVICE_NONE;
+import static android.media.AudioSystem.isBluetoothDevice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * Class representing all devices that were previously or are currently connected. Data is
+ * persisted in {@link android.provider.Settings.Secure}
+ */
+/*package*/ final class AdiDeviceState {
+    private static final String TAG = "AS.AdiDeviceState";
+
+    private static final String SETTING_FIELD_SEPARATOR = ",";
+
+    @AudioDeviceInfo.AudioDeviceType
+    private final int mDeviceType;
+
+    private final int mInternalDeviceType;
+    @NonNull
+    private final String mDeviceAddress;
+    private boolean mSAEnabled;
+    private boolean mHasHeadTracker = false;
+    private boolean mHeadTrackerEnabled;
+
+    /**
+     * Constructor
+     *
+     * @param deviceType external audio device type
+     * @param internalDeviceType if not set pass {@link DEVICE_NONE}, in this case the
+     *                           default conversion of the external type will be used
+     * @param address must be non-null for wireless devices
+     * @throws NullPointerException if a null address is passed for a wireless device
+     */
+    AdiDeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType,
+                        int internalDeviceType,
+                        @Nullable String address) {
+        mDeviceType = deviceType;
+        if (internalDeviceType != DEVICE_NONE) {
+            mInternalDeviceType = internalDeviceType;
+        } else {
+            mInternalDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType);
+
+        }
+        mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull(
+                address) : "";
+    }
+
+    @AudioDeviceInfo.AudioDeviceType
+    public int getDeviceType() {
+        return mDeviceType;
+    }
+
+    public int getInternalDeviceType() {
+        return mInternalDeviceType;
+    }
+
+    @NonNull
+    public String getDeviceAddress() {
+        return mDeviceAddress;
+    }
+
+    public void setSAEnabled(boolean sAEnabled) {
+        mSAEnabled = sAEnabled;
+    }
+
+    public boolean isSAEnabled() {
+        return mSAEnabled;
+    }
+
+    public void setHeadTrackerEnabled(boolean headTrackerEnabled) {
+        mHeadTrackerEnabled = headTrackerEnabled;
+    }
+
+    public boolean isHeadTrackerEnabled() {
+        return mHeadTrackerEnabled;
+    }
+
+    public void setHasHeadTracker(boolean hasHeadTracker) {
+        mHasHeadTracker = hasHeadTracker;
+    }
+
+
+    public boolean hasHeadTracker() {
+        return mHasHeadTracker;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        // type check and cast
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final AdiDeviceState sads = (AdiDeviceState) obj;
+        return mDeviceType == sads.mDeviceType
+                && mInternalDeviceType == sads.mInternalDeviceType
+                && mDeviceAddress.equals(sads.mDeviceAddress)  // NonNull
+                && mSAEnabled == sads.mSAEnabled
+                && mHasHeadTracker == sads.mHasHeadTracker
+                && mHeadTrackerEnabled == sads.mHeadTrackerEnabled;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDeviceType, mInternalDeviceType, mDeviceAddress, mSAEnabled,
+                mHasHeadTracker, mHeadTrackerEnabled);
+    }
+
+    @Override
+    public String toString() {
+        return "type: " + mDeviceType + "internal type: " + mInternalDeviceType
+                + " addr: " + mDeviceAddress + " enabled: " + mSAEnabled
+                + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled;
+    }
+
+    public String toPersistableString() {
+        return (new StringBuilder().append(mDeviceType)
+                .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress)
+                .append(SETTING_FIELD_SEPARATOR).append(mSAEnabled ? "1" : "0")
+                .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0")
+                .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0")
+                .append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType)
+                .toString());
+    }
+
+    /**
+     * Gets the max size (including separators) when persisting the elements with
+     * {@link AdiDeviceState#toPersistableString()}.
+     */
+    public static int getPeristedMaxSize() {
+        return 36;  /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+                           + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
+                           + (SETTINGS_FIELD_SEPARATOR)5 */
+    }
+
+    @Nullable
+    public static AdiDeviceState fromPersistedString(@Nullable String persistedString) {
+        if (persistedString == null) {
+            return null;
+        }
+        if (persistedString.isEmpty()) {
+            return null;
+        }
+        String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR);
+        // we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal
+        // device type
+        if (fields.length != 5 && fields.length != 6) {
+            // expecting all fields, fewer may mean corruption, ignore those settings
+            return null;
+        }
+        try {
+            final int deviceType = Integer.parseInt(fields[0]);
+            int internalDeviceType = -1;
+            if (fields.length == 6) {
+                internalDeviceType = Integer.parseInt(fields[5]);
+            }
+            final AdiDeviceState deviceState = new AdiDeviceState(deviceType,
+                    internalDeviceType, fields[1]);
+            deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1);
+            deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1);
+            deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1);
+            return deviceState;
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e);
+            return null;
+        }
+    }
+
+    public AudioDeviceAttributes getAudioDeviceAttributes() {
+        return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+                mDeviceType, mDeviceAddress);
+    }
+
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 4f45c0d..85fc803 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -50,6 +50,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
@@ -69,8 +70,11 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 
-/** @hide */
-/*package*/ final class AudioDeviceBroker {
+/**
+ * @hide
+ * (non final for mocking/spying)
+ */
+public class AudioDeviceBroker {
 
     private static final String TAG = "AS.AudioDeviceBroker";
 
@@ -1850,6 +1854,9 @@
                     final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
                     BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
                 } break;
+                case MSG_PERSIST_AUDIO_DEVICE_SETTINGS:
+                    onPersistAudioDeviceSettings();
+                    break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
             }
@@ -1927,6 +1934,8 @@
 
     private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
 
+    private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54;
+
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
             case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
@@ -2344,4 +2353,95 @@
                 info.getId(),
                 null /*mixerAttributes*/);
     }
+
+    /**
+     * post a message to persist the audio device settings.
+     * Message is delayed by 1s on purpose in case of successive changes in quick succession (at
+     * init time for instance)
+     * Note this method is made public to work around a Mockito bug where it needs to be public
+     * in order to be mocked by a test a the same package
+     * (see https://code.google.com/archive/p/mockito/issues/127)
+     */
+    public void persistAudioDeviceSettings() {
+        sendMsg(MSG_PERSIST_AUDIO_DEVICE_SETTINGS, SENDMSG_REPLACE, /*delay*/ 1000);
+    }
+
+    void onPersistAudioDeviceSettings() {
+        final String deviceSettings = mDeviceInventory.getDeviceSettings();
+        Log.v(TAG, "saving audio device settings: " + deviceSettings);
+        final SettingsAdapter settings = mAudioService.getSettings();
+        boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(),
+                Settings.Secure.AUDIO_DEVICE_INVENTORY,
+                deviceSettings, UserHandle.USER_CURRENT);
+        if (!res) {
+            Log.e(TAG, "error saving audio device settings: " + deviceSettings);
+        }
+    }
+
+    void onReadAudioDeviceSettings() {
+        final SettingsAdapter settingsAdapter = mAudioService.getSettings();
+        final ContentResolver contentResolver = mAudioService.getContentResolver();
+        String settings = settingsAdapter.getSecureStringForUser(contentResolver,
+                Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT);
+        if (settings == null) {
+            Log.i(TAG, "reading spatial audio device settings from legacy key"
+                    + Settings.Secure.SPATIAL_AUDIO_ENABLED);
+            // legacy string format for key SPATIAL_AUDIO_ENABLED has the same order of fields like
+            // the strings for key AUDIO_DEVICE_INVENTORY. This will ensure to construct valid
+            // device settings when calling {@link #setDeviceSettings()}
+            settings = settingsAdapter.getSecureStringForUser(contentResolver,
+                    Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
+            if (settings == null) {
+                Log.i(TAG, "no spatial audio device settings stored with legacy key");
+            } else if (!settings.equals("")) {
+                // Delete old key value and update the new key
+                if (!settingsAdapter.putSecureStringForUser(contentResolver,
+                        Settings.Secure.SPATIAL_AUDIO_ENABLED,
+                        /*value=*/"",
+                        UserHandle.USER_CURRENT)) {
+                    Log.w(TAG, "cannot erase the legacy audio device settings with key "
+                            + Settings.Secure.SPATIAL_AUDIO_ENABLED);
+                }
+                if (!settingsAdapter.putSecureStringForUser(contentResolver,
+                        Settings.Secure.AUDIO_DEVICE_INVENTORY,
+                        settings,
+                        UserHandle.USER_CURRENT)) {
+                    Log.e(TAG, "error updating the new audio device settings with key "
+                            + Settings.Secure.AUDIO_DEVICE_INVENTORY);
+                }
+            }
+        }
+
+        if (settings != null && !settings.equals("")) {
+            setDeviceSettings(settings);
+        }
+    }
+
+    void setDeviceSettings(String settings) {
+        mDeviceInventory.setDeviceSettings(settings);
+    }
+
+    /** Test only method. */
+    String getDeviceSettings() {
+        return mDeviceInventory.getDeviceSettings();
+    }
+
+    List<AdiDeviceState> getImmutableDeviceInventory() {
+        return mDeviceInventory.getImmutableDeviceInventory();
+    }
+
+    void addDeviceStateToInventory(AdiDeviceState deviceState) {
+        mDeviceInventory.addDeviceStateToInventory(deviceState);
+    }
+
+    AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
+            int canonicalType) {
+        return mDeviceInventory.findDeviceStateForAudioDeviceAttributes(ada, canonicalType);
+    }
+
+    //------------------------------------------------
+    // for testing purposes only
+    void clearDeviceInventory() {
+        mDeviceInventory.clearDeviceInventory();
+    }
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 0c7f11f..b4ab14f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.audio;
 
+import static android.media.AudioSystem.isBluetoothDevice;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
@@ -78,12 +80,51 @@
 
     private static final String TAG = "AS.AudioDeviceInventory";
 
+    private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
+    private static final String SETTING_DEVICE_SEPARATOR = "\\|";
+
     // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
     private final Object mDevicesLock = new Object();
 
     //Audio Analytics ids.
     private static final String mMetricsId = "audio.device.";
 
+    private final Object mDeviceInventoryLock = new Object();
+    @GuardedBy("mDeviceCatalogLock")
+    private final ArrayList<AdiDeviceState> mDeviceInventory = new ArrayList<>(0);
+    List<AdiDeviceState> getImmutableDeviceInventory() {
+        synchronized (mDeviceInventoryLock) {
+            return List.copyOf(mDeviceInventory);
+        }
+    }
+
+    void addDeviceStateToInventory(AdiDeviceState deviceState) {
+        synchronized (mDeviceInventoryLock) {
+            mDeviceInventory.add(deviceState);
+        }
+    }
+
+    AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
+            int canonicalDeviceType) {
+        final boolean isWireless = isBluetoothDevice(ada.getInternalType());
+        synchronized (mDeviceInventoryLock) {
+            for (AdiDeviceState deviceSetting : mDeviceInventory) {
+                if (deviceSetting.getDeviceType() == canonicalDeviceType
+                        && (!isWireless || ada.getAddress().equals(
+                        deviceSetting.getDeviceAddress()))) {
+                    return deviceSetting;
+                }
+            }
+        }
+        return null;
+    }
+
+    void clearDeviceInventory() {
+        synchronized (mDeviceInventoryLock) {
+            mDeviceInventory.clear();
+        }
+    }
+
     // List of connected devices
     // Key for map created from DeviceInfo.makeDeviceListKey()
     @GuardedBy("mDevicesLock")
@@ -341,6 +382,12 @@
         mAppliedPresetRolesInt.forEach((key, devices) -> {
             pw.println("  " + prefix + "preset: " + key.first
                     +  " role:" + key.second + " devices:" + devices); });
+        pw.println("\ndevices:\n");
+        synchronized (mDeviceInventoryLock) {
+            for (AdiDeviceState device : mDeviceInventory) {
+                pw.println("\t" + device + "\n");
+            }
+        }
     }
 
     //------------------------------------------------------------
@@ -1198,7 +1245,7 @@
 
                     AudioDeviceInfo device = Stream.of(connectedDevices)
                             .filter(d -> d.getInternalType() == ada.getInternalType())
-                            .filter(d -> (!AudioSystem.isBluetoothDevice(d.getInternalType())
+                            .filter(d -> (!isBluetoothDevice(d.getInternalType())
                                             || (d.getAddress().equals(ada.getAddress()))))
                             .findFirst()
                             .orElse(null);
@@ -1619,7 +1666,7 @@
         }
 
         for (DeviceInfo di : mConnectedDevices.values()) {
-            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
+            if (!isBluetoothDevice(di.mDeviceType)) {
                 continue;
             }
             AudioDeviceAttributes ada =
@@ -1733,7 +1780,7 @@
         }
         HashSet<String> processedAddresses = new HashSet<>(0);
         for (DeviceInfo di : mConnectedDevices.values()) {
-            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)
+            if (!isBluetoothDevice(di.mDeviceType)
                     || processedAddresses.contains(di.mDeviceAddress)) {
                 continue;
             }
@@ -1743,7 +1790,7 @@
                         + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
             }
             for (DeviceInfo di2 : mConnectedDevices.values()) {
-                if (!AudioSystem.isBluetoothDevice(di2.mDeviceType)
+                if (!isBluetoothDevice(di2.mDeviceType)
                         || !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
                     continue;
                 }
@@ -2359,6 +2406,40 @@
         }
     }
 
+    /*package*/ String getDeviceSettings() {
+        int deviceCatalogSize = 0;
+        synchronized (mDeviceInventoryLock) {
+            deviceCatalogSize = mDeviceInventory.size();
+        }
+        final StringBuilder settingsBuilder = new StringBuilder(
+                deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
+
+        synchronized (mDeviceInventoryLock) {
+            for (int i = 0; i < mDeviceInventory.size(); i++) {
+                settingsBuilder.append(mDeviceInventory.get(i).toPersistableString());
+                if (i != mDeviceInventory.size() - 1) {
+                    settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR);
+                }
+            }
+        }
+        return settingsBuilder.toString();
+    }
+
+    /*package*/ void setDeviceSettings(String settings) {
+        clearDeviceInventory();
+        String[] devSettings = TextUtils.split(Objects.requireNonNull(settings),
+                SETTING_DEVICE_SEPARATOR);
+        // small list, not worth overhead of Arrays.stream(devSettings)
+        for (String setting : devSettings) {
+            AdiDeviceState devState = AdiDeviceState.fromPersistedString(setting);
+            // Note if the device is not compatible with spatialization mode or the device
+            // type is not canonical, it will be ignored in {@link SpatializerHelper}.
+            if (devState != null) {
+                addDeviceStateToInventory(devState);
+            }
+        }
+    }
+
     //----------------------------------------------------------
     // For tests only
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f313e16..71424b1 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -382,7 +382,6 @@
     private static final int MSG_DISPATCH_AUDIO_MODE = 40;
     private static final int MSG_ROUTING_UPDATED = 41;
     private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
-    private static final int MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS = 43;
     private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44;
     private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
     private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
@@ -1022,6 +1021,8 @@
         mAudioPolicy = audioPolicy;
         mPlatformType = AudioSystem.getPlatformType(context);
 
+        mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
+
         mIsSingleVolume = AudioSystem.isSingleVolume(context);
 
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -1034,13 +1035,14 @@
 
         mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
 
-        final boolean binauralEnabledDefault = SystemProperties.getBoolean(
+        boolean binauralEnabledDefault = SystemProperties.getBoolean(
                 "ro.audio.spatializer_binaural_enabled_default", true);
-        final boolean transauralEnabledDefault = SystemProperties.getBoolean(
+        boolean transauralEnabledDefault = SystemProperties.getBoolean(
                 "ro.audio.spatializer_transaural_enabled_default", true);
-        final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
+        boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
-        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem,
+
+        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, mDeviceBroker,
                 binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault);
 
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
@@ -1208,8 +1210,6 @@
         mUseFixedVolume = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
 
-        mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
-
         mRecordMonitor = new RecordingActivityMonitor(mContext);
         mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
 
@@ -6573,6 +6573,10 @@
         return mContentResolver;
     }
 
+    /*package*/ SettingsAdapter getSettings() {
+        return mSettings;
+    }
+
     ///////////////////////////////////////////////////////////////////////////
     // Internal methods
     ///////////////////////////////////////////////////////////////////////////
@@ -9215,10 +9219,6 @@
                     mSpatializerHelper.onInitSensors();
                     break;
 
-                case MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS:
-                    onPersistSpatialAudioDeviceSettings();
-                    break;
-
                 case MSG_RESET_SPATIALIZER:
                     mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
                     break;
@@ -10275,41 +10275,11 @@
     }
 
     void onInitSpatializer() {
-        final String settings = mSettings.getSecureStringForUser(mContentResolver,
-                Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
-        if (settings == null) {
-            Log.e(TAG, "error reading spatial audio device settings");
-        }
-        mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect, settings);
+        mDeviceBroker.onReadAudioDeviceSettings();
+        mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
         mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
     }
 
-    /**
-     * post a message to persist the spatial audio device settings.
-     * Message is delayed by 1s on purpose in case of successive changes in quick succession (at
-     * init time for instance)
-     * Note this method is made public to work around a Mockito bug where it needs to be public
-     * in order to be mocked by a test a the same package
-     * (see https://code.google.com/archive/p/mockito/issues/127)
-     */
-    public void persistSpatialAudioDeviceSettings() {
-        sendMsg(mAudioHandler,
-                MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS,
-                SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, TAG,
-                /*delay*/ 1000);
-    }
-
-    void onPersistSpatialAudioDeviceSettings() {
-        final String settings = mSpatializerHelper.getSADeviceSettings();
-        Log.v(TAG, "saving spatial audio device settings: " + settings);
-        boolean res = mSettings.putSecureStringForUser(mContentResolver,
-                Settings.Secure.SPATIAL_AUDIO_ENABLED,
-                settings, UserHandle.USER_CURRENT);
-        if (!res) {
-            Log.e(TAG, "error saving spatial audio device settings: " + settings);
-        }
-    }
-
     //==========================================================================================
 
     // camera sound is forced if any of the resources corresponding to one active SIM
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 462c938..18f24db 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.server.audio;
 
+import static android.media.AudioSystem.isBluetoothDevice;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -35,10 +37,9 @@
 import android.media.ISpatializerHeadTrackingModeCallback;
 import android.media.ISpatializerOutputCallback;
 import android.media.MediaMetrics;
-import android.media.SpatializationLevel;
-import android.media.SpatializationMode;
 import android.media.Spatializer;
-import android.media.SpatializerHeadTrackingMode;
+import android.media.audio.common.HeadTracking;
+import android.media.audio.common.Spatialization;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.text.TextUtils;
@@ -47,13 +48,13 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.EventLogger;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
-import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -73,38 +74,32 @@
 
     private final @NonNull AudioSystemAdapter mASA;
     private final @NonNull AudioService mAudioService;
+    private final @NonNull AudioDeviceBroker mDeviceBroker;
     private @Nullable SensorManager mSensorManager;
 
     //------------------------------------------------------------
 
-    private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
+    /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
         {
-            append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL);
+            append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_WIRED_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, Spatialization.Mode.BINAURAL);
             // assumption for A2DP: mostly headsets
-            append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL);
-            append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
-            append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_DOCK, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_ACCESSORY, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_DEVICE, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_USB_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_LINE_ANALOG, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_LINE_DIGITAL, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_AUX_LINE, Spatialization.Mode.TRANSAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_HEADSET, Spatialization.Mode.BINAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_SPEAKER, Spatialization.Mode.TRANSAURAL);
             // assumption that BLE broadcast would be mostly consumed on headsets
-            append(AudioDeviceInfo.TYPE_BLE_BROADCAST, SpatializationMode.SPATIALIZER_BINAURAL);
+            append(AudioDeviceInfo.TYPE_BLE_BROADCAST, Spatialization.Mode.BINAURAL);
         }
     };
 
-    private static final int[] WIRELESS_TYPES = { AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
-            AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
-            AudioDeviceInfo.TYPE_BLE_HEADSET,
-            AudioDeviceInfo.TYPE_BLE_SPEAKER,
-            AudioDeviceInfo.TYPE_BLE_BROADCAST
-    };
-
     // Spatializer state machine
     /*package*/ static final int STATE_UNINITIALIZED = 0;
     /*package*/ static final int STATE_NOT_SUPPORTED = 1;
@@ -114,10 +109,15 @@
     /*package*/ static final int STATE_DISABLED_AVAILABLE = 6;
     private int mState = STATE_UNINITIALIZED;
 
+    @VisibleForTesting boolean mBinauralEnabledDefault;
+    @VisibleForTesting boolean mTransauralEnabledDefault;
+    @VisibleForTesting boolean mHeadTrackingEnabledDefault;
+
     private boolean mFeatureEnabled = false;
     /** current level as reported by native Spatializer in callback */
     private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
     private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+
     private boolean mTransauralSupported = false;
     private boolean mBinauralSupported = false;
     private boolean mIsHeadTrackingSupported = false;
@@ -160,31 +160,21 @@
      */
     private final ArrayList<Integer> mSACapableDeviceTypes = new ArrayList<>(0);
 
-    /**
-     * List of devices where Spatial Audio is possible. Each device can be enabled or disabled
-     * (== user choice to use or not)
-     */
-    @GuardedBy("this")
-    private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0);
-
     //------------------------------------------------------
     // initialization
-    @SuppressWarnings("StaticAssignmentInConstructor")
     SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa,
-            boolean binauralEnabledDefault,
-            boolean transauralEnabledDefault,
-            boolean headTrackingEnabledDefault) {
+            @NonNull AudioDeviceBroker deviceBroker, boolean binauralEnabledDefault,
+            boolean transauralEnabledDefault, boolean headTrackingEnabledDefault) {
         mAudioService = mother;
         mASA = asa;
-        // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being
-        // constructed here is the factory for SADeviceState, thus SADeviceState and its
-        // private static field sHeadTrackingEnabledDefault should never be accessed directly.
-        SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault;
-        SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault;
-        SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
+        mDeviceBroker = deviceBroker;
+
+        mBinauralEnabledDefault = binauralEnabledDefault;
+        mTransauralEnabledDefault = transauralEnabledDefault;
+        mHeadTrackingEnabledDefault = headTrackingEnabledDefault;
     }
 
-    synchronized void init(boolean effectExpected, @Nullable String settings) {
+    synchronized void init(boolean effectExpected) {
         loglogi("init effectExpected=" + effectExpected);
         if (!effectExpected) {
             loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected");
@@ -233,12 +223,12 @@
                 ArrayList<Integer> list = new ArrayList<>(0);
                 for (byte value : values) {
                     switch (value) {
-                        case SpatializerHeadTrackingMode.OTHER:
-                        case SpatializerHeadTrackingMode.DISABLED:
+                        case HeadTracking.Mode.OTHER:
+                        case HeadTracking.Mode.DISABLED:
                             // not expected here, skip
                             break;
-                        case SpatializerHeadTrackingMode.RELATIVE_WORLD:
-                        case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+                        case HeadTracking.Mode.RELATIVE_WORLD:
+                        case HeadTracking.Mode.RELATIVE_SCREEN:
                             list.add(headTrackingModeTypeToSpatializerInt(value));
                             break;
                         default:
@@ -261,10 +251,10 @@
             byte[] spatModes = spat.getSupportedModes();
             for (byte mode : spatModes) {
                 switch (mode) {
-                    case SpatializationMode.SPATIALIZER_BINAURAL:
+                    case Spatialization.Mode.BINAURAL:
                         mBinauralSupported = true;
                         break;
-                    case SpatializationMode.SPATIALIZER_TRANSAURAL:
+                    case Spatialization.Mode.TRANSAURAL:
                         mTransauralSupported = true;
                         break;
                     default:
@@ -281,17 +271,18 @@
             // initialize list of compatible devices
             for (int i = 0; i < SPAT_MODE_FOR_DEVICE_TYPE.size(); i++) {
                 int mode = SPAT_MODE_FOR_DEVICE_TYPE.valueAt(i);
-                if ((mode == (int) SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
-                        || (mode == (int) SpatializationMode.SPATIALIZER_TRANSAURAL
+                if ((mode == (int) Spatialization.Mode.BINAURAL && mBinauralSupported)
+                        || (mode == (int) Spatialization.Mode.TRANSAURAL
                             && mTransauralSupported)) {
                     mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i));
                 }
             }
 
-            // When initialized from AudioService, the settings string will be non-null.
-            // Saved settings need to be applied after spatialization support is initialized above.
-            if (settings != null) {
-                setSADeviceSettings(settings);
+            // Log the saved device states that are compatible with SA
+            for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) {
+                if (isSADevice(deviceState)) {
+                    logDeviceState(deviceState, "setSADeviceSettings");
+                }
             }
 
             // for both transaural / binaural, we are not forcing enablement as the init() method
@@ -331,7 +322,7 @@
         mState = STATE_UNINITIALIZED;
         mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
         mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
-        init(true, null /* settings */);
+        init(/*effectExpected=*/true);
         setSpatializerEnabledInt(featureEnabled);
     }
 
@@ -372,7 +363,7 @@
         final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
 
         // is media routed to a new device?
-        if (isWireless(currentDevice.getType())) {
+        if (isBluetoothDevice(currentDevice.getInternalType())) {
             addWirelessDeviceIfNew(currentDevice);
         }
 
@@ -520,8 +511,8 @@
     synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
         // build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices
         ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>();
-        for (SADeviceState deviceState : mSADevices) {
-            if (deviceState.mEnabled) {
+        for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) {
+            if (deviceState.isSAEnabled() && isSADevice(deviceState)) {
                 compatList.add(deviceState.getAudioDeviceAttributes());
             }
         }
@@ -548,31 +539,50 @@
             return;
         }
         loglogi("addCompatibleAudioDevice: dev=" + ada);
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
-        SADeviceState deviceUpdated = null; // non-null on update.
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        initSAState(deviceState);
+        AdiDeviceState updatedDevice = null; // non-null on update.
         if (deviceState != null) {
-            if (forceEnable && !deviceState.mEnabled) {
-                deviceUpdated = deviceState;
-                deviceUpdated.mEnabled = true;
+            if (forceEnable && !deviceState.isSAEnabled()) {
+                updatedDevice = deviceState;
+                updatedDevice.setSAEnabled(true);
             }
         } else {
             // When adding, force the device type to be a canonical one.
-            final int canonicalDeviceType = getCanonicalDeviceType(ada.getType());
+            final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(),
+                    ada.getInternalType());
             if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
                 Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes "
                         + ada);
                 return;
             }
-            deviceUpdated = new SADeviceState(canonicalDeviceType, ada.getAddress());
-            mSADevices.add(deviceUpdated);
+            updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
+                    ada.getAddress());
+            initSAState(updatedDevice);
+            mDeviceBroker.addDeviceStateToInventory(updatedDevice);
         }
-        if (deviceUpdated != null) {
+        if (updatedDevice != null) {
             onRoutingUpdated();
-            mAudioService.persistSpatialAudioDeviceSettings();
-            logDeviceState(deviceUpdated, "addCompatibleAudioDevice");
+            mDeviceBroker.persistAudioDeviceSettings();
+            logDeviceState(updatedDevice, "addCompatibleAudioDevice");
         }
     }
 
+    private void initSAState(AdiDeviceState device) {
+        if (device == null) {
+            return;
+        }
+
+        int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(),
+                Integer.MIN_VALUE);
+        device.setSAEnabled(spatMode == Spatialization.Mode.BINAURAL
+                ? mBinauralEnabledDefault
+                : spatMode == Spatialization.Mode.TRANSAURAL
+                        ? mTransauralEnabledDefault
+                        : false);
+        device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault);
+    }
+
     private static final String METRICS_DEVICE_PREFIX = "audio.spatializer.device.";
 
     // Device logging is accomplished in the Java Audio Service level.
@@ -580,29 +590,30 @@
     //
     // There may be different devices with the same device type (aliasing).
     // We always send the full device state info on each change.
-    private void logDeviceState(SADeviceState deviceState, String event) {
+    static void logDeviceState(AdiDeviceState deviceState, String event) {
         final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
-                deviceState.mDeviceType);
+                deviceState.getDeviceType());
         final String deviceName = AudioSystem.getDeviceName(deviceType);
         new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName)
-            .set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress)
-            .set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false")
-            .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event))
-            .set(MediaMetrics.Property.HAS_HEAD_TRACKER,
-                    deviceState.mHasHeadTracker ? "true" : "false") // this may be updated later.
-            .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED,
-                    deviceState.mHeadTrackerEnabled ? "true" : "false")
-            .record();
+                .set(MediaMetrics.Property.ADDRESS, deviceState.getDeviceAddress())
+                .set(MediaMetrics.Property.ENABLED, deviceState.isSAEnabled() ? "true" : "false")
+                .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event))
+                .set(MediaMetrics.Property.HAS_HEAD_TRACKER,
+                        deviceState.hasHeadTracker() ? "true"
+                                : "false") // this may be updated later.
+                .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED,
+                        deviceState.isHeadTrackerEnabled() ? "true" : "false")
+                .record();
     }
 
     synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
         loglogi("removeCompatibleAudioDevice: dev=" + ada);
 
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
-        if (deviceState != null && deviceState.mEnabled) {
-            deviceState.mEnabled = false;
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        if (deviceState != null && deviceState.isSAEnabled()) {
+            deviceState.setSAEnabled(false);
             onRoutingUpdated();
-            mAudioService.persistSpatialAudioDeviceSettings();
+            mDeviceBroker.persistAudioDeviceSettings();
             logDeviceState(deviceState, "removeCompatibleAudioDevice");
         }
     }
@@ -611,13 +622,14 @@
      * Returns a possibly aliased device type which is used
      * for spatial audio settings (or TYPE_UNKNOWN  if it doesn't exist).
      */
-    private static @AudioDeviceInfo.AudioDeviceType int getCanonicalDeviceType(int deviceType) {
-        if (isWireless(deviceType)) return deviceType;
+    @AudioDeviceInfo.AudioDeviceType
+    private static int getCanonicalDeviceType(int deviceType, int internalDeviceType) {
+        if (isBluetoothDevice(internalDeviceType)) return deviceType;
 
         final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
-        if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
+        if (spatMode == Spatialization.Mode.TRANSAURAL) {
             return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
-        } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) {
+        } else if (spatMode == Spatialization.Mode.BINAURAL) {
             return AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
         }
         return AudioDeviceInfo.TYPE_UNKNOWN;
@@ -629,18 +641,9 @@
      */
     @GuardedBy("this")
     @Nullable
-    private SADeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) {
-        final int deviceType = ada.getType();
-        final boolean isWireless = isWireless(deviceType);
-        final int canonicalDeviceType = getCanonicalDeviceType(deviceType);
-
-        for (SADeviceState deviceState : mSADevices) {
-            if (deviceState.mDeviceType == canonicalDeviceType
-                    && (!isWireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
-                return deviceState;
-            }
-        }
-        return null;
+    private AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) {
+        return mDeviceBroker.findDeviceStateForAudioDeviceAttributes(ada,
+                getCanonicalDeviceType(ada.getType(), ada.getInternalType()));
     }
 
     /**
@@ -662,14 +665,14 @@
             Log.e(TAG, "no spatialization mode found for device type:" + deviceType);
             return new Pair<>(false, false);
         }
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
         if (deviceState == null) {
             // no matching device state?
             Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada);
             return new Pair<>(false, false);
         }
         // found the matching device state.
-        return new Pair<>(deviceState.mEnabled, true /* available */);
+        return new Pair<>(deviceState.isSAEnabled(), true /* available */);
     }
 
     private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) {
@@ -678,16 +681,19 @@
         }
         if (findDeviceStateForAudioDeviceAttributes(ada) == null) {
             // wireless device types should be canonical, but we translate to be sure.
-            final int canonicalDeviceType = getCanonicalDeviceType((ada.getType()));
+            final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(),
+                    ada.getInternalType());
             if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
                 Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes "
                         + ada);
                 return;
             }
-            final SADeviceState deviceState =
-                    new SADeviceState(canonicalDeviceType, ada.getAddress());
-            mSADevices.add(deviceState);
-            mAudioService.persistSpatialAudioDeviceSettings();
+            final AdiDeviceState deviceState =
+                    new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
+                            ada.getAddress());
+            initSAState(deviceState);
+            mDeviceBroker.addDeviceStateToInventory(deviceState);
+            mDeviceBroker.persistAudioDeviceSettings();
             logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later.
         }
     }
@@ -748,14 +754,20 @@
         // not be included.
         final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
                 /*default when type not found*/ -1);
-        if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
-                || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
+        if ((modeForDevice == Spatialization.Mode.BINAURAL && mBinauralSupported)
+                || (modeForDevice == Spatialization.Mode.TRANSAURAL
                         && mTransauralSupported)) {
             return true;
         }
         return false;
     }
 
+    private boolean isSADevice(AdiDeviceState deviceState) {
+        return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(),
+                deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes(
+                deviceState.getAudioDeviceAttributes());
+    }
+
     synchronized void setFeatureEnabled(boolean enabled) {
         loglogi("setFeatureEnabled(" + enabled + ") was featureEnabled:" + mFeatureEnabled);
         if (mFeatureEnabled == enabled) {
@@ -768,7 +780,7 @@
                 return;
             }
             if (mState == STATE_UNINITIALIZED) {
-                init(true, null /* settings */);
+                init(true);
             }
             setSpatializerEnabledInt(true);
         } else {
@@ -1137,16 +1149,16 @@
             Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled
                     + " for " + ada);
         }
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
         if (deviceState == null) return;
-        if (!deviceState.mHasHeadTracker) {
+        if (!deviceState.hasHeadTracker()) {
             Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
                     + " device:" + ada + " on a device without headtracker");
             return;
         }
         Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
-        deviceState.mHeadTrackerEnabled = enabled;
-        mAudioService.persistSpatialAudioDeviceSettings();
+        deviceState.setHeadTrackerEnabled(enabled);
+        mDeviceBroker.persistAudioDeviceSettings();
         logDeviceState(deviceState, "setHeadTrackerEnabled");
 
         // check current routing to see if it affects the headtracking mode
@@ -1170,8 +1182,8 @@
             Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada);
             return false;
         }
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
-        return deviceState != null && deviceState.mHasHeadTracker;
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        return deviceState != null && deviceState.hasHeadTracker();
     }
 
     /**
@@ -1184,14 +1196,14 @@
             Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada);
             return false;
         }
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
         if (deviceState != null) {
-            if (!deviceState.mHasHeadTracker) {
-                deviceState.mHasHeadTracker = true;
-                mAudioService.persistSpatialAudioDeviceSettings();
+            if (!deviceState.hasHeadTracker()) {
+                deviceState.setHasHeadTracker(true);
+                mDeviceBroker.persistAudioDeviceSettings();
                 logDeviceState(deviceState, "setHasHeadTracker");
             }
-            return deviceState.mHeadTrackerEnabled;
+            return deviceState.isHeadTrackerEnabled();
         }
         Log.e(TAG, "setHasHeadTracker: device not found for:" + ada);
         return false;
@@ -1202,9 +1214,9 @@
             Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada);
             return false;
         }
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
         return deviceState != null
-                && deviceState.mHasHeadTracker && deviceState.mHeadTrackerEnabled;
+                && deviceState.hasHeadTracker() && deviceState.isHeadTrackerEnabled();
     }
 
     synchronized boolean isHeadTrackerAvailable() {
@@ -1417,7 +1429,7 @@
     }
 
     synchronized void onInitSensors() {
-        final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE);
+        final boolean init = mFeatureEnabled && (mSpatLevel != Spatialization.Level.NONE);
         final String action = init ? "initializing" : "releasing";
         if (mSpat == null) {
             logloge("not " + action + " sensors, null spatializer");
@@ -1483,13 +1495,13 @@
     // SDK <-> AIDL converters
     private static int headTrackingModeTypeToSpatializerInt(byte mode) {
         switch (mode) {
-            case SpatializerHeadTrackingMode.OTHER:
+            case HeadTracking.Mode.OTHER:
                 return Spatializer.HEAD_TRACKING_MODE_OTHER;
-            case SpatializerHeadTrackingMode.DISABLED:
+            case HeadTracking.Mode.DISABLED:
                 return Spatializer.HEAD_TRACKING_MODE_DISABLED;
-            case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+            case HeadTracking.Mode.RELATIVE_WORLD:
                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
-            case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+            case HeadTracking.Mode.RELATIVE_SCREEN:
                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
             default:
                 throw (new IllegalArgumentException("Unexpected head tracking mode:" + mode));
@@ -1499,13 +1511,13 @@
     private static byte spatializerIntToHeadTrackingModeType(int sdkMode) {
         switch (sdkMode) {
             case Spatializer.HEAD_TRACKING_MODE_OTHER:
-                return SpatializerHeadTrackingMode.OTHER;
+                return HeadTracking.Mode.OTHER;
             case Spatializer.HEAD_TRACKING_MODE_DISABLED:
-                return SpatializerHeadTrackingMode.DISABLED;
+                return HeadTracking.Mode.DISABLED;
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
-                return SpatializerHeadTrackingMode.RELATIVE_WORLD;
+                return HeadTracking.Mode.RELATIVE_WORLD;
             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
-                return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
+                return HeadTracking.Mode.RELATIVE_SCREEN;
             default:
                 throw (new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
         }
@@ -1513,11 +1525,11 @@
 
     private static int spatializationLevelToSpatializerInt(byte level) {
         switch (level) {
-            case SpatializationLevel.NONE:
+            case Spatialization.Level.NONE:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
-            case SpatializationLevel.SPATIALIZER_MULTICHANNEL:
+            case Spatialization.Level.MULTICHANNEL:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL;
-            case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS:
+            case Spatialization.Level.BED_PLUS_OBJECTS:
                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS;
             default:
                 throw (new IllegalArgumentException("Unexpected spatializer level:" + level));
@@ -1543,144 +1555,6 @@
         pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:"
                 + mTransauralSupported);
         pw.println("\tmSpatOutput:" + mSpatOutput);
-        pw.println("\tdevices:");
-        for (SADeviceState device : mSADevices) {
-            pw.println("\t\t" + device);
-        }
-    }
-
-    /*package*/ static final class SADeviceState {
-        private static boolean sBinauralEnabledDefault = true;
-        private static boolean sTransauralEnabledDefault = true;
-        private static boolean sHeadTrackingEnabledDefault = false;
-        final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
-        final @NonNull String mDeviceAddress;
-        boolean mEnabled;
-        boolean mHasHeadTracker = false;
-        boolean mHeadTrackerEnabled;
-        static final String SETTING_FIELD_SEPARATOR = ",";
-        static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
-        static final String SETTING_DEVICE_SEPARATOR = "\\|";
-
-        /**
-         * Constructor
-         * @param deviceType
-         * @param address must be non-null for wireless devices
-         * @throws NullPointerException if a null address is passed for a wireless device
-         */
-        SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) {
-            mDeviceType = deviceType;
-            mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
-            final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
-            mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL
-                    ? sBinauralEnabledDefault
-                    : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
-                            ? sTransauralEnabledDefault
-                            : false;
-            mHeadTrackerEnabled = sHeadTrackingEnabledDefault;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null) {
-                return false;
-            }
-            // type check and cast
-            if (getClass() != obj.getClass()) {
-                return false;
-            }
-            final SADeviceState sads = (SADeviceState) obj;
-            return mDeviceType == sads.mDeviceType
-                    && mDeviceAddress.equals(sads.mDeviceAddress)
-                    && mEnabled == sads.mEnabled
-                    && mHasHeadTracker == sads.mHasHeadTracker
-                    && mHeadTrackerEnabled == sads.mHeadTrackerEnabled;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mDeviceType, mDeviceAddress, mEnabled, mHasHeadTracker,
-                    mHeadTrackerEnabled);
-        }
-
-        @Override
-        public String toString() {
-            return "type: " + mDeviceType + " addr: " + mDeviceAddress + " enabled: " + mEnabled
-                    + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled;
-        }
-
-        String toPersistableString() {
-            return (new StringBuilder().append(mDeviceType)
-                    .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress)
-                    .append(SETTING_FIELD_SEPARATOR).append(mEnabled ? "1" : "0")
-                    .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0")
-                    .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0")
-                    .toString());
-        }
-
-        static @Nullable SADeviceState fromPersistedString(@Nullable String persistedString) {
-            if (persistedString == null) {
-                return null;
-            }
-            if (persistedString.isEmpty()) {
-                return null;
-            }
-            String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR);
-            if (fields.length != 5) {
-                // expecting all fields, fewer may mean corruption, ignore those settings
-                return null;
-            }
-            try {
-                final int deviceType = Integer.parseInt(fields[0]);
-                final SADeviceState deviceState = new SADeviceState(deviceType, fields[1]);
-                deviceState.mEnabled = Integer.parseInt(fields[2]) == 1;
-                deviceState.mHasHeadTracker = Integer.parseInt(fields[3]) == 1;
-                deviceState.mHeadTrackerEnabled = Integer.parseInt(fields[4]) == 1;
-                return deviceState;
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "unable to parse setting for SADeviceState: " + persistedString, e);
-                return null;
-            }
-        }
-
-        public AudioDeviceAttributes getAudioDeviceAttributes() {
-            return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
-                    mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
-        }
-
-    }
-
-    /*package*/ synchronized String getSADeviceSettings() {
-        // expected max size of each String for each SADeviceState is 25 (accounting for separator)
-        final StringBuilder settingsBuilder = new StringBuilder(mSADevices.size() * 25);
-        for (int i = 0; i < mSADevices.size(); i++) {
-            settingsBuilder.append(mSADevices.get(i).toPersistableString());
-            if (i != mSADevices.size() - 1) {
-                settingsBuilder.append(SADeviceState.SETTING_DEVICE_SEPARATOR_CHAR);
-            }
-        }
-        return settingsBuilder.toString();
-    }
-
-    /*package*/ synchronized void setSADeviceSettings(@NonNull String persistedSettings) {
-        String[] devSettings = TextUtils.split(Objects.requireNonNull(persistedSettings),
-                SADeviceState.SETTING_DEVICE_SEPARATOR);
-        // small list, not worth overhead of Arrays.stream(devSettings)
-        for (String setting : devSettings) {
-            SADeviceState devState = SADeviceState.fromPersistedString(setting);
-            // Note if the device is not compatible with spatialization mode
-            // or the device type is not canonical, it is ignored.
-            if (devState != null
-                    && devState.mDeviceType == getCanonicalDeviceType(devState.mDeviceType)
-                    && isDeviceCompatibleWithSpatializationModes(
-                            devState.getAudioDeviceAttributes())) {
-                mSADevices.add(devState);
-                logDeviceState(devState, "setSADeviceSettings");
-            }
-        }
     }
 
     private static String spatStateString(int state) {
@@ -1702,15 +1576,6 @@
         }
     }
 
-    private static boolean isWireless(@AudioDeviceInfo.AudioDeviceType int deviceType) {
-        for (int type : WIRELESS_TYPES) {
-            if (type == deviceType) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private int getHeadSensorHandleUpdateTracker() {
         int headHandle = -1;
         if (sRoutingDevices.isEmpty()) {
@@ -1780,11 +1645,6 @@
 
     //------------------------------------------------
     // for testing purposes only
-
-    /*package*/ void clearSADevices() {
-        mSADevices.clear();
-    }
-
     /*package*/ synchronized void forceStateForTest(int state) {
         mState = state;
     }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 4538cad..0629e637 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -39,6 +39,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.ComponentInfoInternal;
+import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.IAuthService;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricService;
@@ -333,6 +334,33 @@
         }
 
         @Override
+        public long getLastAuthenticationTime(int userId,
+                @Authenticators.Types int authenticators) throws RemoteException {
+            // Only allow internal clients to call getLastAuthenticationTime with a different
+            // userId.
+            final int callingUserId = UserHandle.getCallingUserId();
+
+            if (userId != callingUserId) {
+                checkInternalPermission();
+            } else {
+                checkPermission();
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                // We can't do this above because we need the READ_DEVICE_CONFIG permission, which
+                // the calling user may not possess.
+                if (!Flags.lastAuthenticationTime()) {
+                    throw new UnsupportedOperationException();
+                }
+
+                return mBiometricService.getLastAuthenticationTime(userId, authenticators);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public boolean hasEnrolledBiometrics(int userId, String opPackageName)
                 throws RemoteException {
             checkInternalPermission();
diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManager.java b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
new file mode 100644
index 0000000..058ea6b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
@@ -0,0 +1,32 @@
+/*
+ * 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.biometrics;
+
+/**
+ * Interface for biometrics to get camera status.
+ */
+public interface BiometricCameraManager {
+    /**
+     * Returns true if any camera is in use.
+     */
+    boolean isAnyCameraUnavailable();
+
+    /**
+     * Returns true if privacy is enabled and camera access is disabled.
+     */
+    boolean isCameraPrivacyEnabled();
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
new file mode 100644
index 0000000..000ee54
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
@@ -0,0 +1,68 @@
+/*
+ * 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.biometrics;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.annotation.NonNull;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraManager;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class BiometricCameraManagerImpl implements BiometricCameraManager {
+
+    private final CameraManager mCameraManager;
+    private final SensorPrivacyManager mSensorPrivacyManager;
+    private final ConcurrentHashMap<String, Boolean> mIsCameraAvailable = new ConcurrentHashMap<>();
+
+    private final CameraManager.AvailabilityCallback mCameraAvailabilityCallback =
+            new CameraManager.AvailabilityCallback() {
+                @Override
+                public void onCameraAvailable(@NonNull String cameraId) {
+                    mIsCameraAvailable.put(cameraId, true);
+                }
+
+                @Override
+                public void onCameraUnavailable(@NonNull String cameraId) {
+                    mIsCameraAvailable.put(cameraId, false);
+                }
+            };
+
+    public BiometricCameraManagerImpl(@NonNull CameraManager cameraManager,
+            @NonNull SensorPrivacyManager sensorPrivacyManager) {
+        mCameraManager = cameraManager;
+        mSensorPrivacyManager = sensorPrivacyManager;
+        mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, null);
+    }
+
+    @Override
+    public boolean isAnyCameraUnavailable() {
+        for (String cameraId : mIsCameraAvailable.keySet()) {
+            if (!mIsCameraAvailable.get(cameraId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isCameraPrivacyEnabled() {
+        return mSensorPrivacyManager != null && mSensorPrivacyManager
+                .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 0942d85..9c454ae 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
+import static android.hardware.biometrics.BiometricManager.BIOMETRIC_NO_AUTHENTICATION;
 
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
 
@@ -33,9 +34,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -47,8 +50,10 @@
 import android.hardware.biometrics.ITestSessionCallback;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.camera2.CameraManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.security.keymint.HardwareAuthenticatorType;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -58,10 +63,16 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.security.Authorization;
+import android.security.GateKeeper;
 import android.security.KeyStore;
+import android.security.authorization.IKeystoreAuthorization;
+import android.security.authorization.ResponseCode;
+import android.service.gatekeeper.IGateKeeperService;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -75,6 +86,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.utils.Slogf;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -112,6 +124,10 @@
     KeyStore mKeyStore;
     @VisibleForTesting
     ITrustManager mTrustManager;
+    @VisibleForTesting
+    IKeystoreAuthorization mKeystoreAuthorization;
+    @VisibleForTesting
+    IGateKeeperService mGateKeeper;
 
     // Get and cache the available biometric authenticators and their associated info.
     final ArrayList<BiometricSensor> mSensors = new ArrayList<>();
@@ -124,6 +140,8 @@
     AuthSession mAuthSession;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
+    private final BiometricCameraManager mBiometricCameraManager;
+
     /**
      * Tracks authenticatorId invalidation. For more details, see
      * {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}.
@@ -626,6 +644,64 @@
         }
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override // Binder call
+        public long getLastAuthenticationTime(
+                int userId, @Authenticators.Types int authenticators) {
+            super.getLastAuthenticationTime_enforcePermission();
+
+            if (!Flags.lastAuthenticationTime()) {
+                throw new UnsupportedOperationException();
+            }
+
+            Slogf.d(TAG, "getLastAuthenticationTime(userId=%d, authenticators=0x%x)",
+                    userId, authenticators);
+
+            final long secureUserId;
+            try {
+                secureUserId = mGateKeeper.getSecureUserId(userId);
+            } catch (RemoteException e) {
+                Slogf.w(TAG, "Failed to get secure user id for " + userId, e);
+                return BIOMETRIC_NO_AUTHENTICATION;
+            }
+
+            if (secureUserId == GateKeeper.INVALID_SECURE_USER_ID) {
+                Slogf.w(TAG, "No secure user id for " + userId);
+                return BIOMETRIC_NO_AUTHENTICATION;
+            }
+
+            ArrayList<Integer> hardwareAuthenticators = new ArrayList<>(2);
+
+            if ((authenticators & Authenticators.DEVICE_CREDENTIAL) != 0) {
+                hardwareAuthenticators.add(HardwareAuthenticatorType.PASSWORD);
+            }
+
+            if ((authenticators & Authenticators.BIOMETRIC_STRONG) != 0) {
+                hardwareAuthenticators.add(HardwareAuthenticatorType.FINGERPRINT);
+            }
+
+            if (hardwareAuthenticators.isEmpty()) {
+                throw new IllegalArgumentException("authenticators must not be empty");
+            }
+
+            int[] authTypesArray = hardwareAuthenticators.stream()
+                    .mapToInt(Integer::intValue)
+                    .toArray();
+            try {
+                return mKeystoreAuthorization.getLastAuthTime(secureUserId, authTypesArray);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Error getting last auth time: " + e);
+                return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+            } catch (ServiceSpecificException e) {
+                // This is returned when the feature flag test fails in keystore2
+                if (e.errorCode == ResponseCode.PERMISSION_DENIED) {
+                    throw new UnsupportedOperationException();
+                }
+
+                return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
+            }
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
 
@@ -933,7 +1009,7 @@
 
         return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
                 userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
-                getContext());
+                getContext(), mBiometricCameraManager);
     }
 
     /**
@@ -947,6 +1023,14 @@
             return ActivityManager.getService();
         }
 
+        public IKeystoreAuthorization getKeystoreAuthorizationService() {
+            return Authorization.getService();
+        }
+
+        public IGateKeeperService getGateKeeperService() {
+            return GateKeeper.getService();
+        }
+
         public ITrustManager getTrustManager() {
             return ITrustManager.Stub.asInterface(ServiceManager.getService(Context.TRUST_SERVICE));
         }
@@ -1026,6 +1110,11 @@
         public UserManager getUserManager(Context context) {
             return context.getSystemService(UserManager.class);
         }
+
+        public BiometricCameraManager getBiometricCameraManager(Context context) {
+            return new BiometricCameraManagerImpl(context.getSystemService(CameraManager.class),
+                    context.getSystemService(SensorPrivacyManager.class));
+        }
     }
 
     /**
@@ -1054,6 +1143,9 @@
         mRequestCounter = mInjector.getRequestGenerator();
         mBiometricContext = injector.getBiometricContext(context);
         mUserManager = injector.getUserManager(context);
+        mBiometricCameraManager = injector.getBiometricCameraManager(context);
+        mKeystoreAuthorization = injector.getKeystoreAuthorizationService();
+        mGateKeeper = injector.getGateKeeperService();
 
         try {
             injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1290,7 +1382,7 @@
                 final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
                         mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
                         opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
-                        getContext());
+                        getContext(), mBiometricCameraManager);
 
                 final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
 
@@ -1300,9 +1392,7 @@
                         + promptInfo.isIgnoreEnrollmentState());
                 // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
                 // be shown for this case.
-                if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
-                        || preAuthStatus.second
-                        == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
+                if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
                     // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
                     // CREDENTIAL is requested and available, set the bundle to only request
                     // CREDENTIAL.
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 3813fd1..b603fcb 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -27,7 +27,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustManager;
 import android.content.Context;
-import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.PromptInfo;
@@ -73,13 +72,16 @@
     final Context context;
     private final boolean mBiometricRequested;
     private final int mBiometricStrengthRequested;
+    private final BiometricCameraManager mBiometricCameraManager;
+
     private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
             boolean credentialRequested, List<BiometricSensor> eligibleSensors,
             List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
             boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
-            Context context) {
+            Context context, BiometricCameraManager biometricCameraManager) {
         mBiometricRequested = biometricRequested;
         mBiometricStrengthRequested = biometricStrengthRequested;
+        mBiometricCameraManager = biometricCameraManager;
         this.credentialRequested = credentialRequested;
 
         this.eligibleSensors = eligibleSensors;
@@ -96,7 +98,8 @@
             BiometricService.SettingObserver settingObserver,
             List<BiometricSensor> sensors,
             int userId, PromptInfo promptInfo, String opPackageName,
-            boolean checkDevicePolicyManager, Context context)
+            boolean checkDevicePolicyManager, Context context,
+            BiometricCameraManager biometricCameraManager)
             throws RemoteException {
 
         final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -124,7 +127,7 @@
                         checkDevicePolicyManager, requestedStrength,
                         promptInfo.getAllowedSensorIds(),
                         promptInfo.isIgnoreEnrollmentState(),
-                        context);
+                        biometricCameraManager);
 
                 Slog.d(TAG, "Package: " + opPackageName
                         + " Sensor ID: " + sensor.id
@@ -138,7 +141,7 @@
                 //
                 // Note: if only a certain sensor is required and the privacy is enabled,
                 // canAuthenticate() will return false.
-                if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
+                if (status == AUTHENTICATOR_OK) {
                     eligibleSensors.add(sensor);
                 } else {
                     ineligibleSensors.add(new Pair<>(sensor, status));
@@ -148,7 +151,7 @@
 
         return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
                 eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
-                promptInfo.isIgnoreEnrollmentState(), userId, context);
+                promptInfo.isIgnoreEnrollmentState(), userId, context, biometricCameraManager);
     }
 
     /**
@@ -165,12 +168,16 @@
             BiometricSensor sensor, int userId, String opPackageName,
             boolean checkDevicePolicyManager, int requestedStrength,
             @NonNull List<Integer> requestedSensorIds,
-            boolean ignoreEnrollmentState, Context context) {
+            boolean ignoreEnrollmentState, BiometricCameraManager biometricCameraManager) {
 
         if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
             return BIOMETRIC_NO_HARDWARE;
         }
 
+        if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
+            return BIOMETRIC_HARDWARE_NOT_DETECTED;
+        }
+
         final boolean wasStrongEnough =
                 Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
         final boolean isStrongEnough =
@@ -191,12 +198,10 @@
                     && !ignoreEnrollmentState) {
                 return BIOMETRIC_NOT_ENROLLED;
             }
-            final SensorPrivacyManager sensorPrivacyManager = context
-                    .getSystemService(SensorPrivacyManager.class);
 
-            if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
-                if (sensorPrivacyManager
-                        .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
+            if (biometricCameraManager != null && sensor.modality == TYPE_FACE) {
+                if (biometricCameraManager.isCameraPrivacyEnabled()) {
+                    //Camera privacy is enabled as the access is disabled
                     return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                 }
             }
@@ -292,13 +297,9 @@
         @AuthenticatorStatus final int status;
         @BiometricAuthenticator.Modality int modality = TYPE_NONE;
 
-        final SensorPrivacyManager sensorPrivacyManager = context
-                .getSystemService(SensorPrivacyManager.class);
-
         boolean cameraPrivacyEnabled = false;
-        if (sensorPrivacyManager != null) {
-            cameraPrivacyEnabled = sensorPrivacyManager
-                    .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
+        if (mBiometricCameraManager != null) {
+            cameraPrivacyEnabled = mBiometricCameraManager.isCameraPrivacyEnabled();
         }
 
         if (mBiometricRequested && credentialRequested) {
@@ -315,7 +316,7 @@
                     // and the face sensor privacy is enabled then return
                     // BIOMETRIC_SENSOR_PRIVACY_ENABLED.
                     //
-                    // Note: This sensor will still be eligible for calls to authenticate.
+                    // Note: This sensor will not be eligible for calls to authenticate.
                     status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                 } else {
                     status = AUTHENTICATOR_OK;
@@ -340,7 +341,7 @@
                     // If the only modality requested is face and the privacy is enabled
                     // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
                     //
-                    // Note: This sensor will still be eligible for calls to authenticate.
+                    // Note: This sensor will not be eligible for calls to authenticate.
                     status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
                 } else {
                     status = AUTHENTICATOR_OK;
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index f8f0088..3c885a1 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -53,6 +53,8 @@
 import android.hardware.usb.UsbManager;
 import android.media.AudioManager;
 import android.nfc.INfcAdapter;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -162,10 +164,6 @@
      * SCALER_ROTATE_AND_CROP_NONE  -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE
      */
 
-    // Flags arguments to NFC adapter to enable/disable NFC
-    public static final int DISABLE_POLLING_FLAGS = 0x1000;
-    public static final int ENABLE_POLLING_FLAGS = 0x0000;
-
     // Handler message codes
     private static final int MSG_SWITCH_USER = 1;
     private static final int MSG_NOTIFY_DEVICE_STATE = 2;
@@ -215,7 +213,6 @@
     private final List<CameraUsageEvent> mCameraUsageHistory = new ArrayList<>();
 
     private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc";
-    private static final String NFC_SERVICE_BINDER_NAME = "nfc";
     private static final IBinder nfcInterfaceToken = new Binder();
 
     private final boolean mNotifyNfc;
@@ -1255,8 +1252,13 @@
         }
     }
 
-    private void notifyNfcService(boolean enablePolling) {
-
+    // TODO(b/303286040): Remove the raw INfcAdapter usage once |ENABLE_NFC_MAINLINE_FLAG| is
+    // rolled out.
+    private static final String NFC_SERVICE_BINDER_NAME = "nfc";
+    // Flags arguments to NFC adapter to enable/disable NFC
+    public static final int DISABLE_POLLING_FLAGS = 0x1000;
+    public static final int ENABLE_POLLING_FLAGS = 0x0000;
+    private void setNfcReaderModeUsingINfcAdapter(boolean enablePolling) {
         IBinder nfcServiceBinder = getBinderService(NFC_SERVICE_BINDER_NAME);
         if (nfcServiceBinder == null) {
             Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
@@ -1272,6 +1274,25 @@
         }
     }
 
+    private void notifyNfcService(boolean enablePolling) {
+        if (android.nfc.Flags.enableNfcMainline()) {
+            NfcManager nfcManager = mContext.getSystemService(NfcManager.class);
+            if (nfcManager == null) {
+                Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
+                return;
+            }
+            NfcAdapter nfcAdapter = nfcManager.getDefaultAdapter();
+            if (nfcAdapter == null) {
+                Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
+                return;
+            }
+            if (DEBUG) Slog.v(TAG, "Setting NFC reader mode. enablePolling: " + enablePolling);
+            nfcAdapter.setReaderMode(enablePolling);
+        } else {
+            setNfcReaderModeUsingINfcAdapter(enablePolling);
+        }
+    }
+
     private static int[] toArray(Collection<Integer> c) {
         int len = c.size();
         int[] ret = new int[len];
diff --git a/services/core/java/com/android/server/compat/overrides/OWNERS b/services/core/java/com/android/server/compat/overrides/OWNERS
index b80f340..6ca7803 100644
--- a/services/core/java/com/android/server/compat/overrides/OWNERS
+++ b/services/core/java/com/android/server/compat/overrides/OWNERS
@@ -1,2 +1,2 @@
-tomnatan@google.com
+mcarli@google.com
 mariiasand@google.com
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 53fbe8f..a12243b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -22,7 +22,6 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
 import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
@@ -45,12 +44,10 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -113,7 +110,6 @@
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.CancellationSignal;
-import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -152,7 +148,6 @@
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
-import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BinderUtils;
 import com.android.net.module.util.LinkPropertiesUtils;
 import com.android.net.module.util.NetdUtils;
@@ -202,7 +197,6 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * @hide
@@ -1063,8 +1057,6 @@
         // Store mPackage since it might be reset or might be replaced with the other VPN app.
         final String oldPackage = mPackage;
         final boolean isPackageChanged = !Objects.equals(packageName, oldPackage);
-        // TODO: Remove "SdkLevel.isAtLeastT()" check once VpnManagerService is decoupled from
-        //  ConnectivityServiceTest.
         // Only notify VPN apps that were already always-on, and only if the always-on provider
         // changed, or the lockdown mode changed.
         final boolean shouldNotifyOldPkg = isVpnApp(oldPackage) && mAlwaysOn
@@ -1078,12 +1070,6 @@
 
         saveAlwaysOnPackage();
 
-        // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
-        //  ConnectivityServiceTest.
-        if (!SdkLevel.isAtLeastT()) {
-            return true;
-        }
-
         if (shouldNotifyOldPkg) {
             // If both of shouldNotifyOldPkg & isPackageChanged are true, that means the
             // always-on of old package is disabled or the old package is replaced with the new
@@ -1984,9 +1970,7 @@
         for (String app : packageNames) {
             int uid = getAppUid(mContext, app, userId);
             if (uid != -1) uids.add(uid);
-            // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
-            // ConnectivityServiceTest.
-            if (Process.isApplicationUid(uid) && SdkLevel.isAtLeastT()) {
+            if (Process.isApplicationUid(uid)) {
                 uids.add(Process.toSdkSandboxUid(uid));
             }
         }
@@ -2297,15 +2281,6 @@
 
     private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() {
         @Override
-        public void interfaceStatusChanged(String interfaze, boolean up) {
-            synchronized (Vpn.this) {
-                if (!up && mVpnRunner != null && mVpnRunner instanceof LegacyVpnRunner) {
-                    ((LegacyVpnRunner) mVpnRunner).exitIfOuterInterfaceIs(interfaze);
-                }
-            }
-        }
-
-        @Override
         public void interfaceRemoved(String interfaze) {
             synchronized (Vpn.this) {
                 if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
@@ -2556,17 +2531,6 @@
     private native boolean jniAddAddress(String interfaze, String address, int prefixLen);
     private native boolean jniDelAddress(String interfaze, String address, int prefixLen);
 
-    private static RouteInfo findIPv4DefaultRoute(LinkProperties prop) {
-        for (RouteInfo route : prop.getAllRoutes()) {
-            // Currently legacy VPN only works on IPv4.
-            if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
-                return route;
-            }
-        }
-
-        throw new IllegalStateException("Unable to find IPv4 default gateway");
-    }
-
     private void enforceNotRestrictedUser() {
         final long token = Binder.clearCallingIdentity();
         try {
@@ -2665,10 +2629,6 @@
             throw new SecurityException("Restricted users cannot establish VPNs");
         }
 
-        final RouteInfo ipv4DefaultRoute = findIPv4DefaultRoute(egress);
-        final String gateway = ipv4DefaultRoute.getGateway().getHostAddress();
-        final String iface = ipv4DefaultRoute.getInterface();
-
         // Load certificates.
         String privateKey = "";
         String userCert = "";
@@ -2700,8 +2660,6 @@
             throw new IllegalStateException("Cannot load credentials");
         }
 
-        // Prepare arguments for racoon.
-        String[] racoon = null;
         switch (profile.type) {
             case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
                 // Secret key is still just the alias (not the actual private key). The private key
@@ -2731,109 +2689,9 @@
                 // profile.
                 startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN);
                 return;
-            case VpnProfile.TYPE_L2TP_IPSEC_PSK:
-                racoon = new String[] {
-                    iface, profile.server, "udppsk", profile.ipsecIdentifier,
-                    profile.ipsecSecret, "1701",
-                };
-                break;
-            case VpnProfile.TYPE_L2TP_IPSEC_RSA:
-                racoon = new String[] {
-                    iface, profile.server, "udprsa", makeKeystoreEngineGrantString(privateKey),
-                    userCert, caCert, serverCert, "1701",
-                };
-                break;
-            case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
-                racoon = new String[] {
-                    iface, profile.server, "xauthpsk", profile.ipsecIdentifier,
-                    profile.ipsecSecret, profile.username, profile.password, "", gateway,
-                };
-                break;
-            case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
-                racoon = new String[] {
-                    iface, profile.server, "xauthrsa", makeKeystoreEngineGrantString(privateKey),
-                    userCert, caCert, serverCert, profile.username, profile.password, "", gateway,
-                };
-                break;
-            case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
-                racoon = new String[] {
-                    iface, profile.server, "hybridrsa",
-                    caCert, serverCert, profile.username, profile.password, "", gateway,
-                };
-                break;
         }
 
-        // Prepare arguments for mtpd. MTU/MRU calculated conservatively. Only IPv4 supported
-        // because LegacyVpn.
-        // 1500 - 60 (Carrier-internal IPv6 + UDP + GTP) - 10 (PPP) - 16 (L2TP) - 8 (UDP)
-        //   - 77 (IPsec w/ SHA-2 512, 256b trunc-len, AES-CBC) - 8 (UDP encap) - 20 (IPv4)
-        //   - 28 (464xlat)
-        String[] mtpd = null;
-        switch (profile.type) {
-            case VpnProfile.TYPE_PPTP:
-                mtpd = new String[] {
-                    iface, "pptp", profile.server, "1723",
-                    "name", profile.username, "password", profile.password,
-                    "linkname", "vpn", "refuse-eap", "nodefaultroute",
-                    "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270",
-                    (profile.mppe ? "+mppe" : "nomppe"),
-                };
-                if (profile.mppe) {
-                    // Disallow PAP authentication when MPPE is requested, as MPPE cannot work
-                    // with PAP anyway, and users may not expect PAP (plain text) to be used when
-                    // MPPE was requested.
-                    mtpd = Arrays.copyOf(mtpd, mtpd.length + 1);
-                    mtpd[mtpd.length - 1] = "-pap";
-                }
-                break;
-            case VpnProfile.TYPE_L2TP_IPSEC_PSK:
-            case VpnProfile.TYPE_L2TP_IPSEC_RSA:
-                mtpd = new String[] {
-                    iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
-                    "name", profile.username, "password", profile.password,
-                    "linkname", "vpn", "refuse-eap", "nodefaultroute",
-                    "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270",
-                };
-                break;
-        }
-
-        VpnConfig config = new VpnConfig();
-        config.legacy = true;
-        config.user = profile.key;
-        config.interfaze = iface;
-        config.session = profile.name;
-        config.isMetered = false;
-        config.proxyInfo = profile.proxy;
-        if (underlying != null) {
-            config.underlyingNetworks = new Network[] { underlying };
-        }
-
-        config.addLegacyRoutes(profile.routes);
-        if (!profile.dnsServers.isEmpty()) {
-            config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
-        }
-        if (!profile.searchDomains.isEmpty()) {
-            config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
-        }
-        startLegacyVpn(config, racoon, mtpd, profile);
-    }
-
-    private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd,
-            VpnProfile profile) {
-        stopVpnRunnerPrivileged();
-
-        // Prepare for the new request.
-        prepareInternal(VpnConfig.LEGACY_VPN);
-        updateState(DetailedState.CONNECTING, "startLegacyVpn");
-
-        // Start a new LegacyVpnRunner and we are done!
-        mVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile);
-        startLegacyVpnRunner();
-    }
-
-    @VisibleForTesting
-    protected void startLegacyVpnRunner() {
-        mVpnRunner.start();
+        throw new UnsupportedOperationException("Legacy VPN is deprecated");
     }
 
     /**
@@ -2851,17 +2709,7 @@
             return;
         }
 
-        final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner;
         mVpnRunner.exit();
-
-        // LegacyVpn uses daemons that must be shut down before new ones are brought up.
-        // The same limitation does not apply to Platform VPNs.
-        if (isLegacyVpn) {
-            synchronized (LegacyVpnRunner.TAG) {
-                // wait for old thread to completely finish before spinning up
-                // new instance, otherwise state updates can be out of order.
-            }
-        }
     }
 
     /**
@@ -4143,9 +3991,7 @@
                 // Ignore stale runner.
                 if (mVpnRunner != this) return;
 
-                // TODO(b/230548427): Remove SDK check once VPN related stuff are
-                //  decoupled from ConnectivityServiceTest.
-                if (SdkLevel.isAtLeastT() && category != null && isVpnApp(mPackage)) {
+                if (category != null && isVpnApp(mPackage)) {
                     sendEventToVpnManagerApp(category, errorClass, errorCode,
                             getPackage(), mSessionKey, makeVpnProfileStateLocked(),
                             mActiveNetwork,
@@ -4256,343 +4102,6 @@
         }
     }
 
-    /**
-     * Bringing up a VPN connection takes time, and that is all this thread
-     * does. Here we have plenty of time. The only thing we need to take
-     * care of is responding to interruptions as soon as possible. Otherwise
-     * requests will pile up. This could be done in a Handler as a state
-     * machine, but it is much easier to read in the current form.
-     */
-    private class LegacyVpnRunner extends VpnRunner {
-        private static final String TAG = "LegacyVpnRunner";
-
-        private final String[] mDaemons;
-        private final String[][] mArguments;
-        private final LocalSocket[] mSockets;
-        private final String mOuterInterface;
-        private final AtomicInteger mOuterConnection =
-                new AtomicInteger(ConnectivityManager.TYPE_NONE);
-        private final VpnProfile mProfile;
-
-        private long mBringupStartTime = -1;
-
-        /**
-         * Watch for the outer connection (passing in the constructor) going away.
-         */
-        private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (!mEnableTeardown) return;
-
-                if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
-                    if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
-                            ConnectivityManager.TYPE_NONE) == mOuterConnection.get()) {
-                        NetworkInfo info = (NetworkInfo)intent.getExtra(
-                                ConnectivityManager.EXTRA_NETWORK_INFO);
-                        if (info != null && !info.isConnectedOrConnecting()) {
-                            try {
-                                mObserver.interfaceStatusChanged(mOuterInterface, false);
-                            } catch (RemoteException e) {}
-                        }
-                    }
-                }
-            }
-        };
-
-        // GuardedBy("Vpn.this") (annotation can't be applied to constructor)
-        LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) {
-            super(TAG);
-            if (racoon == null && mtpd == null) {
-                throw new IllegalArgumentException(
-                        "Arguments to racoon and mtpd must not both be null");
-            }
-            mConfig = config;
-            mDaemons = new String[] {"racoon", "mtpd"};
-            // TODO: clear arguments from memory once launched
-            mArguments = new String[][] {racoon, mtpd};
-            mSockets = new LocalSocket[mDaemons.length];
-
-            // This is the interface which VPN is running on,
-            // mConfig.interfaze will change to point to OUR
-            // internal interface soon. TODO - add inner/outer to mconfig
-            // TODO - we have a race - if the outer iface goes away/disconnects before we hit this
-            // we will leave the VPN up.  We should check that it's still there/connected after
-            // registering
-            mOuterInterface = mConfig.interfaze;
-
-            mProfile = profile;
-
-            if (!TextUtils.isEmpty(mOuterInterface)) {
-                for (Network network : mConnectivityManager.getAllNetworks()) {
-                    final LinkProperties lp = mConnectivityManager.getLinkProperties(network);
-                    if (lp != null && lp.getAllInterfaceNames().contains(mOuterInterface)) {
-                        final NetworkInfo netInfo = mConnectivityManager.getNetworkInfo(network);
-                        if (netInfo != null) {
-                            mOuterConnection.set(netInfo.getType());
-                            break;
-                        }
-                    }
-                }
-            }
-
-            IntentFilter filter = new IntentFilter();
-            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-            mContext.registerReceiver(mBroadcastReceiver, filter);
-        }
-
-        /**
-         * Checks if the parameter matches the underlying interface
-         *
-         * <p>If the underlying interface is torn down, the LegacyVpnRunner also should be. It has
-         * no ability to migrate between interfaces (or Networks).
-         */
-        public void exitIfOuterInterfaceIs(String interfaze) {
-            if (interfaze.equals(mOuterInterface)) {
-                Log.i(TAG, "Legacy VPN is going down with " + interfaze);
-                exitVpnRunner();
-            }
-        }
-
-        /** Tears down this LegacyVpn connection */
-        @Override
-        public void exitVpnRunner() {
-            // We assume that everything is reset after stopping the daemons.
-            interrupt();
-
-            // Always disconnect. This may be called again in cleanupVpnStateLocked() if
-            // exitVpnRunner() was called from exit(), but it will be a no-op.
-            agentDisconnect();
-            try {
-                mContext.unregisterReceiver(mBroadcastReceiver);
-            } catch (IllegalArgumentException e) {}
-        }
-
-        @Override
-        public void run() {
-            // Wait for the previous thread since it has been interrupted.
-            Log.v(TAG, "Waiting");
-            synchronized (TAG) {
-                Log.v(TAG, "Executing");
-                try {
-                    bringup();
-                    waitForDaemonsToStop();
-                    interrupted(); // Clear interrupt flag if execute called exit.
-                } catch (InterruptedException e) {
-                } finally {
-                    for (LocalSocket socket : mSockets) {
-                        IoUtils.closeQuietly(socket);
-                    }
-                    // This sleep is necessary for racoon to successfully complete sending delete
-                    // message to server.
-                    try {
-                        Thread.sleep(50);
-                    } catch (InterruptedException e) {
-                    }
-                    for (String daemon : mDaemons) {
-                        mDeps.stopService(daemon);
-                    }
-                }
-                agentDisconnect();
-            }
-        }
-
-        private void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException {
-            long now = SystemClock.elapsedRealtime();
-            if (now - mBringupStartTime <= 60000) {
-                Thread.sleep(sleepLonger ? 200 : 1);
-            } else {
-                updateState(DetailedState.FAILED, "checkpoint");
-                throw new IllegalStateException("VPN bringup took too long");
-            }
-        }
-
-        private void checkAndFixupArguments(@NonNull final InetAddress endpointAddress) {
-            final String endpointAddressString = endpointAddress.getHostAddress();
-            // Perform some safety checks before inserting the address in place.
-            // Position 0 in mDaemons and mArguments must be racoon, and position 1 must be mtpd.
-            if (!"racoon".equals(mDaemons[0]) || !"mtpd".equals(mDaemons[1])) {
-                throw new IllegalStateException("Unexpected daemons order");
-            }
-
-            // Respectively, the positions at which racoon and mtpd take the server address
-            // argument are 1 and 2. Not all types of VPN require both daemons however, and
-            // in that case the corresponding argument array is null.
-            if (mArguments[0] != null) {
-                if (!mProfile.server.equals(mArguments[0][1])) {
-                    throw new IllegalStateException("Invalid server argument for racoon");
-                }
-                mArguments[0][1] = endpointAddressString;
-            }
-
-            if (mArguments[1] != null) {
-                if (!mProfile.server.equals(mArguments[1][2])) {
-                    throw new IllegalStateException("Invalid server argument for mtpd");
-                }
-                mArguments[1][2] = endpointAddressString;
-            }
-        }
-
-        private void bringup() {
-            // Catch all exceptions so we can clean up a few things.
-            try {
-                // resolve never returns null. If it does because of some bug, it will be
-                // caught by the catch() block below and cleanup gracefully.
-                final InetAddress endpointAddress = mDeps.resolve(mProfile.server);
-
-                // Big hack : dynamically replace the address of the server in the arguments
-                // with the resolved address.
-                checkAndFixupArguments(endpointAddress);
-
-                // Initialize the timer.
-                mBringupStartTime = SystemClock.elapsedRealtime();
-
-                // Wait for the daemons to stop.
-                for (String daemon : mDaemons) {
-                    while (!mDeps.isServiceStopped(daemon)) {
-                        checkInterruptAndDelay(true);
-                    }
-                }
-
-                // Clear the previous state.
-                final File state = mDeps.getStateFile();
-                state.delete();
-                if (state.exists()) {
-                    throw new IllegalStateException("Cannot delete the state");
-                }
-                new File("/data/misc/vpn/abort").delete();
-
-                updateState(DetailedState.CONNECTING, "execute");
-
-                // Start the daemon with arguments.
-                for (int i = 0; i < mDaemons.length; ++i) {
-                    String[] arguments = mArguments[i];
-                    if (arguments == null) {
-                        continue;
-                    }
-
-                    // Start the daemon.
-                    String daemon = mDaemons[i];
-                    mDeps.startService(daemon);
-
-                    // Wait for the daemon to start.
-                    while (!mDeps.isServiceRunning(daemon)) {
-                        checkInterruptAndDelay(true);
-                    }
-
-                    // Create the control socket.
-                    mSockets[i] = new LocalSocket();
-
-                    // Wait for the socket to connect and send over the arguments.
-                    mDeps.sendArgumentsToDaemon(daemon, mSockets[i], arguments,
-                            this::checkInterruptAndDelay);
-                }
-
-                // Wait for the daemons to create the new state.
-                while (!state.exists()) {
-                    // Check if a running daemon is dead.
-                    for (int i = 0; i < mDaemons.length; ++i) {
-                        String daemon = mDaemons[i];
-                        if (mArguments[i] != null && !mDeps.isServiceRunning(daemon)) {
-                            throw new IllegalStateException(daemon + " is dead");
-                        }
-                    }
-                    checkInterruptAndDelay(true);
-                }
-
-                // Now we are connected. Read and parse the new state.
-                String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
-                if (parameters.length != 7) {
-                    throw new IllegalStateException("Cannot parse the state: '"
-                            + String.join("', '", parameters) + "'");
-                }
-
-                // Set the interface and the addresses in the config.
-                synchronized (Vpn.this) {
-                    mConfig.interfaze = parameters[0].trim();
-
-                    mConfig.addLegacyAddresses(parameters[1]);
-                    // Set the routes if they are not set in the config.
-                    if (mConfig.routes == null || mConfig.routes.isEmpty()) {
-                        mConfig.addLegacyRoutes(parameters[2]);
-                    }
-
-                    // Set the DNS servers if they are not set in the config.
-                    if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
-                        String dnsServers = parameters[3].trim();
-                        if (!dnsServers.isEmpty()) {
-                            mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
-                        }
-                    }
-
-                    // Set the search domains if they are not set in the config.
-                    if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
-                        String searchDomains = parameters[4].trim();
-                        if (!searchDomains.isEmpty()) {
-                            mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
-                        }
-                    }
-
-                    // Add a throw route for the VPN server endpoint, if one was specified.
-                    if (endpointAddress instanceof Inet4Address) {
-                        mConfig.routes.add(new RouteInfo(
-                                new IpPrefix(endpointAddress, 32), null /*gateway*/,
-                                null /*iface*/, RTN_THROW));
-                    } else if (endpointAddress instanceof Inet6Address) {
-                        mConfig.routes.add(new RouteInfo(
-                                new IpPrefix(endpointAddress, 128), null /*gateway*/,
-                                null /*iface*/, RTN_THROW));
-                    } else {
-                        Log.e(TAG, "Unknown IP address family for VPN endpoint: "
-                                + endpointAddress);
-                    }
-
-                    // Here is the last step and it must be done synchronously.
-                    // Set the start time
-                    mConfig.startTime = SystemClock.elapsedRealtime();
-
-                    // Check if the thread was interrupted while we were waiting on the lock.
-                    checkInterruptAndDelay(false);
-
-                    // Check if the interface is gone while we are waiting.
-                    if (!mDeps.isInterfacePresent(Vpn.this, mConfig.interfaze)) {
-                        throw new IllegalStateException(mConfig.interfaze + " is gone");
-                    }
-
-                    // Now INetworkManagementEventObserver is watching our back.
-                    mInterface = mConfig.interfaze;
-                    prepareStatusIntent();
-
-                    agentConnect();
-
-                    Log.i(TAG, "Connected!");
-                }
-            } catch (Exception e) {
-                Log.i(TAG, "Aborting", e);
-                updateState(DetailedState.FAILED, e.getMessage());
-                exitVpnRunner();
-            }
-        }
-
-        /**
-         * Check all daemons every two seconds. Return when one of them is stopped.
-         * The caller will move to the disconnected state when this function returns,
-         * which can happen if a daemon failed or if the VPN was torn down.
-         */
-        private void waitForDaemonsToStop() throws InterruptedException {
-            if (!mNetworkInfo.isConnected()) {
-                return;
-            }
-            while (true) {
-                Thread.sleep(2000);
-                for (int i = 0; i < mDaemons.length; i++) {
-                    if (mArguments[i] != null && mDeps.isServiceStopped(mDaemons[i])) {
-                        return;
-                    }
-                }
-            }
-        }
-    }
-
     private void verifyCallingUidAndPackage(String packageName) {
         mDeps.verifyCallingUidAndPackage(mContext, packageName, mUserId);
     }
@@ -4839,11 +4348,9 @@
         // Build intent first because the sessionKey will be reset after performing
         // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
         // VpnRunner.exit() to prevent design being changed in the future.
-        // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
-        //  ConnectivityServiceTest.
         final int ownerUid = mOwnerUID;
         Intent intent = null;
-        if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
+        if (isVpnApp(mPackage)) {
             intent = buildVpnManagerEventIntent(
                     VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
                     -1 /* errorClass */, -1 /* errorCode*/, mPackage,
@@ -4884,12 +4391,8 @@
         // The underlying network, NetworkCapabilities and LinkProperties are not
         // necessary to send to VPN app since the purpose of this event is to notify
         // VPN app that VPN is deactivated by the user.
-        // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
-        //  ConnectivityServiceTest.
-        if (SdkLevel.isAtLeastT()) {
-            mEventChanges.log("[VMEvent] " + packageName + " stopped");
-            sendEventToVpnManagerApp(intent, packageName);
-        }
+        mEventChanges.log("[VMEvent] " + packageName + " stopped");
+        sendEventToVpnManagerApp(intent, packageName);
     }
 
     private boolean storeAppExclusionList(@NonNull String packageName,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 09d0bc7..fa95a34 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -31,7 +31,6 @@
 import static android.os.UserHandle.USER_SYSTEM;
 
 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_PASSWORD_OR_PIN;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
@@ -40,8 +39,12 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
 import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
+import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
 import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
+import static com.android.internal.widget.LockPatternUtils.isSpecialUserId;
+import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityToCredentialType;
 import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
 import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
 import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK;
@@ -237,6 +240,10 @@
     private static final String LSKF_LAST_CHANGED_TIME_KEY = "sp-handle-ts";
     private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
 
+    private static final String MIGRATED_FRP2 = "migrated_frp2";
+    private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
+    private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
+
     // Duration that LockSettingsService will store the gatekeeper password for. This allows
     // multiple biometric enrollments without prompting the user to enter their password via
     // ConfirmLockPassword/ConfirmLockPattern multiple times. This needs to be at least the duration
@@ -308,8 +315,6 @@
     protected IGateKeeperService mGateKeeperService;
     protected IAuthSecret mAuthSecretService;
 
-    private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
-
     /**
      * The UIDs that are used for system credential storage in keystore.
      */
@@ -340,6 +345,8 @@
             super.onBootPhase(phase);
             if (phase == PHASE_ACTIVITY_MANAGER_READY) {
                 mLockSettingsService.migrateOldDataAfterSystemReady();
+                mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
+            } else if (phase == PHASE_BOOT_COMPLETED) {
                 mLockSettingsService.loadEscrowData();
             }
         }
@@ -571,7 +578,7 @@
         }
 
         public boolean isGsiRunning() {
-            return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
+            return LockPatternUtils.isGsiRunning();
         }
 
         public FingerprintManager getFingerprintManager() {
@@ -687,9 +694,9 @@
             return;
         }
 
-        if (isUserKeyUnlocked(userId)) {
-            // If storage is not locked, the user will be automatically unlocked so there is
-            // no need to show the notification.
+        if (isCeStorageUnlocked(userId)) {
+            // If the user's CE storage is already unlocked, then the user will be automatically
+            // unlocked, so there is no need to show the notification.
             return;
         }
 
@@ -903,14 +910,14 @@
     }
 
     private void migrateOldData() {
-        if (getString("migrated_keystore_namespace", null, 0) == null) {
+        if (getString(MIGRATED_KEYSTORE_NS, null, 0) == null) {
             boolean success = true;
             synchronized (mSpManager) {
                 success &= mSpManager.migrateKeyNamespace();
             }
             success &= migrateProfileLockKeys();
             if (success) {
-                setString("migrated_keystore_namespace", "true", 0);
+                setString(MIGRATED_KEYSTORE_NS, "true", 0);
                 Slog.i(TAG, "Migrated keys to LSS namespace");
             } else {
                 Slog.w(TAG, "Failed to migrate keys to LSS namespace");
@@ -930,9 +937,9 @@
         // "migrated_frp" to "migrated_frp2" to cause migrateFrpCredential() to run again on devices
         // where it had run before.
         if (LockPatternUtils.frpCredentialEnabled(mContext)
-                && !getBoolean("migrated_frp2", false, 0)) {
+                && !getBoolean(MIGRATED_FRP2, false, 0)) {
             migrateFrpCredential();
-            setBoolean("migrated_frp2", true, 0);
+            setBoolean(MIGRATED_FRP2, true, 0);
         }
     }
 
@@ -979,6 +986,16 @@
         return success;
     }
 
+    @VisibleForTesting
+    void deleteRepairModePersistentDataIfNeeded() {
+        if (!LockPatternUtils.isRepairModeSupported(mContext)
+                || LockPatternUtils.isRepairModeActive(mContext)
+                || mInjector.isGsiRunning()) {
+            return;
+        }
+        mStorage.deleteRepairModePersistentData();
+    }
+
     // This is called when Weaver is guaranteed to be available (if the device supports Weaver).
     // It does any synthetic password related work that was delayed from earlier in the boot.
     private void onThirdPartyAppsStarted() {
@@ -1010,16 +1027,16 @@
             // they did have an SP then their CE key wasn't encrypted by it.
             //
             // If this gets interrupted (e.g. by the device powering off), there shouldn't be a
-            // problem since this will run again on the next boot, and setUserKeyProtection() is
-            // okay with the key being already protected by the given secret.
-            if (getString("migrated_all_users_to_sp_and_bound_ce", null, 0) == null) {
+            // problem since this will run again on the next boot, and setCeStorageProtection() is
+            // okay with the CE key being already protected by the given secret.
+            if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) {
                 for (UserInfo user : mUserManager.getAliveUsers()) {
                     removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
                     synchronized (mSpManager) {
                         migrateUserToSpWithBoundCeKeyLocked(user.id);
                     }
                 }
-                setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
+                setString(MIGRATED_SP_CE_ONLY, "true", 0);
             }
 
             mThirdPartyAppsStarted = true;
@@ -1046,7 +1063,7 @@
                 Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
                 return;
             }
-            setUserKeyProtection(userId, result.syntheticPassword.deriveFileBasedEncryptionKey());
+            setCeStorageProtection(userId, result.syntheticPassword);
         }
     }
 
@@ -1305,8 +1322,8 @@
      * {@link #CREDENTIAL_TYPE_PASSWORD}
      */
     private int getCredentialTypeInternal(int userId) {
-        if (userId == USER_FRP) {
-            return getFrpCredentialType();
+        if (isSpecialUserId(userId)) {
+            return mSpManager.getSpecialUserCredentialType(userId);
         }
         synchronized (mSpManager) {
             final long protectorId = getCurrentLskfBasedProtectorId(userId);
@@ -1322,29 +1339,6 @@
         }
     }
 
-    private int getFrpCredentialType() {
-        PersistentData data = mStorage.readPersistentDataBlock();
-        if (data.type != PersistentData.TYPE_SP_GATEKEEPER &&
-                data.type != PersistentData.TYPE_SP_WEAVER) {
-            return CREDENTIAL_TYPE_NONE;
-        }
-        int credentialType = SyntheticPasswordManager.getFrpCredentialType(data.payload);
-        if (credentialType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
-            return credentialType;
-        }
-        return pinOrPasswordQualityToCredentialType(data.qualityForUi);
-    }
-
-    private static int pinOrPasswordQualityToCredentialType(int quality) {
-        if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
-            return CREDENTIAL_TYPE_PASSWORD;
-        }
-        if (LockPatternUtils.isQualityNumericPin(quality)) {
-            return CREDENTIAL_TYPE_PIN;
-        }
-        throw new IllegalArgumentException("Quality is neither Pin nor password: " + quality);
-    }
-
     private boolean isUserSecure(int userId) {
         return getCredentialTypeInternal(userId) != CREDENTIAL_TYPE_NONE;
     }
@@ -1354,8 +1348,8 @@
         AndroidKeyStoreMaintenance.onUserPasswordChanged(userHandle, password);
     }
 
-    private void unlockKeystore(byte[] password, int userHandle) {
-        Authorization.onLockScreenEvent(false, userHandle, password, null);
+    private void unlockKeystore(int userId, SyntheticPassword sp) {
+        Authorization.onLockScreenEvent(false, userId, sp.deriveKeyStorePassword(), null);
     }
 
     @VisibleForTesting /** Note: this method is overridden in unit tests */
@@ -1586,8 +1580,8 @@
      * unlock operation.
      */
     private void sendCredentialsOnUnlockIfRequired(LockscreenCredential credential, int userId) {
-        // Don't send credentials during the factory reset protection flow.
-        if (userId == USER_FRP) {
+        // Don't send credentials during the special user flow.
+        if (isSpecialUserId(userId)) {
             return;
         }
 
@@ -2008,10 +2002,11 @@
         mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext));
     }
 
-    private void setUserKeyProtection(@UserIdInt int userId, byte[] secret) {
+    private void setCeStorageProtection(@UserIdInt int userId, SyntheticPassword sp) {
+        final byte[] secret = sp.deriveFileBasedEncryptionKey();
         final long callingId = Binder.clearCallingIdentity();
         try {
-            mStorageManager.setUserKeyProtection(userId, secret);
+            mStorageManager.setCeStorageProtection(userId, secret);
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to protect CE key for user " + userId, e);
         } finally {
@@ -2019,11 +2014,11 @@
         }
     }
 
-    private boolean isUserKeyUnlocked(int userId) {
+    private boolean isCeStorageUnlocked(int userId) {
         try {
-            return mStorageManager.isUserKeyUnlocked(userId);
+            return mStorageManager.isCeStorageUnlocked(userId);
         } catch (RemoteException e) {
-            Slog.e(TAG, "failed to check user key locked state", e);
+            Slog.e(TAG, "Error checking whether CE storage is unlocked", e);
             return false;
         }
     }
@@ -2034,8 +2029,8 @@
      * This method doesn't throw exceptions because it is called opportunistically whenever a user
      * is started.  Whether it worked or not can be detected by whether the key got unlocked or not.
      */
-    private void unlockUserKey(@UserIdInt int userId, SyntheticPassword sp) {
-        if (isUserKeyUnlocked(userId)) {
+    private void unlockCeStorage(@UserIdInt int userId, SyntheticPassword sp) {
+        if (isCeStorageUnlocked(userId)) {
             Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
             return;
         }
@@ -2043,7 +2038,7 @@
         final String userType = isUserSecure(userId) ? "secured" : "unsecured";
         final byte[] secret = sp.deriveFileBasedEncryptionKey();
         try {
-            mStorageManager.unlockUserKey(userId, userInfo.serialNumber, secret);
+            mStorageManager.unlockCeStorage(userId, userInfo.serialNumber, 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);
@@ -2052,10 +2047,14 @@
         }
     }
 
-    private void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+    @Override
+    public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+        checkPasswordReadPermission();
         synchronized (mSpManager) {
-            if (isUserKeyUnlocked(userId)) {
+            if (isCeStorageUnlocked(userId)) {
                 Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
+                // This method actually does more than unlock CE storage.  However, if CE storage is
+                // already unlocked, then the other parts must have already been done too.
                 return;
             }
             if (isUserSecure(userId)) {
@@ -2072,7 +2071,7 @@
                 return;
             }
             onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
-            unlockUserKey(userId, result.syntheticPassword);
+            unlockCeStorage(userId, result.syntheticPassword);
         }
     }
 
@@ -2208,15 +2207,19 @@
             Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
             return VerifyCredentialResponse.ERROR;
         }
+        if (userId == USER_REPAIR_MODE && !LockPatternUtils.isRepairModeActive(mContext)) {
+            Slog.e(TAG, "Repair mode is not active on the device.");
+            return VerifyCredentialResponse.ERROR;
+        }
         Slogf.i(TAG, "Verifying lockscreen credential for user %d", userId);
 
         final AuthenticationResult authResult;
         VerifyCredentialResponse response;
 
         synchronized (mSpManager) {
-            if (userId == USER_FRP) {
-                return mSpManager.verifyFrpCredential(getGateKeeperService(), credential,
-                        progressCallback);
+            if (isSpecialUserId(userId)) {
+                return mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(),
+                        credential, progressCallback);
             }
 
             long protectorId = getCurrentLskfBasedProtectorId(userId);
@@ -2225,6 +2228,12 @@
             response = authResult.gkResponse;
 
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+                if ((flags & VERIFY_FLAG_WRITE_REPAIR_MODE_PW) != 0) {
+                    if (!mSpManager.writeRepairModeCredentialLocked(protectorId, userId)) {
+                        Slog.e(TAG, "Failed to write repair mode credential");
+                        return VerifyCredentialResponse.ERROR;
+                    }
+                }
                 // credential has matched
                 mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
                         authResult.syntheticPassword.deriveGkPassword());
@@ -2765,7 +2774,7 @@
             final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
                     LockscreenCredential.createNone(), sp, userId);
             setCurrentLskfBasedProtectorId(protectorId, userId);
-            setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
+            setCeStorageProtection(userId, sp);
             onSyntheticPasswordCreated(userId, sp);
             Slogf.i(TAG, "Successfully initialized synthetic password for user %d", userId);
             return sp;
@@ -2824,9 +2833,9 @@
             }
         }
 
-        unlockKeystore(sp.deriveKeyStorePassword(), userId);
+        unlockKeystore(userId, sp);
 
-        unlockUserKey(userId, sp);
+        unlockCeStorage(userId, sp);
 
         unlockUser(userId);
 
@@ -2890,8 +2899,8 @@
 
             mSpManager.clearSidForUser(userId);
             gateKeeperClearSecureUserId(userId);
-            unlockUserKey(userId, sp);
-            unlockKeystore(sp.deriveKeyStorePassword(), userId);
+            unlockCeStorage(userId, sp);
+            unlockKeystore(userId, sp);
             setKeystorePassword(null, userId);
             removeBiometricsForUser(userId);
         }
@@ -3451,11 +3460,6 @@
         }
 
         @Override
-        public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
-            LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
-        }
-
-        @Override
         public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
             LockSettingsService.this.createNewUser(userId, userSerialNumber);
         }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index df95c69..4bac872 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -174,7 +174,7 @@
             pw.println("    Sets the lock screen as PIN, using the given PIN to unlock.");
             pw.println("");
             pw.println("  set-password [--old <CREDENTIAL>] [--user USER_ID] <PASSWORD>");
-            pw.println("    Sets the lock screen as password, using the given PASSOWRD to unlock.");
+            pw.println("    Sets the lock screen as password, using the given PASSWORD to unlock.");
             pw.println("");
             pw.println("  clear [--old <CREDENTIAL>] [--user USER_ID]");
             pw.println("    Clears the lock credentials.");
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 18f84c0..1e8b387 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -19,7 +19,7 @@
 import static android.content.Context.USER_SERVICE;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.isSpecialUserId;
 
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyManager;
@@ -90,6 +90,9 @@
 
     private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
 
+    private static final String REPAIR_MODE_DIRECTORY = "repair-mode/";
+    private static final String REPAIR_MODE_PERSISTENT_FILE = "pst";
+
     private static final Object DEFAULT = new Object();
 
     private static final String[] SETTINGS_TO_BACKUP = new String[] {
@@ -390,6 +393,29 @@
         }
     }
 
+    @VisibleForTesting
+    File getRepairModePersistentDataFile() {
+        final File directory = new File(Environment.getMetadataDirectory(), REPAIR_MODE_DIRECTORY);
+        return new File(directory, REPAIR_MODE_PERSISTENT_FILE);
+    }
+
+    public PersistentData readRepairModePersistentData() {
+        final byte[] data = readFile(getRepairModePersistentDataFile());
+        if (data == null) {
+            return PersistentData.NONE;
+        }
+        return PersistentData.fromBytes(data);
+    }
+
+    public void writeRepairModePersistentData(int persistentType, int userId, byte[] payload) {
+        writeFile(getRepairModePersistentDataFile(),
+                PersistentData.toBytes(persistentType, userId, /* qualityForUi= */0, payload));
+    }
+
+    public void deleteRepairModePersistentData() {
+        deleteFile(getRepairModePersistentDataFile());
+    }
+
     /**
      * Writes the synthetic password state file for the given user ID, protector ID, and state name.
      * If the file already exists, then it is atomically replaced.
@@ -510,7 +536,8 @@
     }
 
     public void setString(String key, String value, int userId) {
-        Preconditions.checkArgument(userId != USER_FRP, "cannot store lock settings for FRP user");
+        Preconditions.checkArgument(!isSpecialUserId(userId),
+                "cannot store lock settings for special user: %d", userId);
 
         writeKeyValue(key, value, userId);
         if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
@@ -535,7 +562,7 @@
     }
 
     public String getString(String key, String defaultValue, int userId) {
-        if (userId == USER_FRP) {
+        if (isSpecialUserId(userId)) {
             return null;
         }
         return readKeyValue(key, defaultValue, userId);
@@ -583,6 +610,17 @@
         }
     }
 
+    /**
+     * Provides a concrete data structure to represent the minimal information from
+     * a user's LSKF-based SP protector that is needed to verify the user's LSKF,
+     * in combination with the corresponding Gatekeeper enrollment or Weaver slot.
+     * It can be stored in {@link com.android.server.PersistentDataBlockService} for
+     * FRP to live across factory resets not initiated via the Settings UI.
+     * Written to {@link #REPAIR_MODE_PERSISTENT_FILE} to support verification for
+     * exiting repair mode, since the device runs with an empty data partition in
+     * repair mode and the same credential be provided to exit repair mode is
+     * required.
+     */
     public static class PersistentData {
         static final byte VERSION_1 = 1;
         static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
@@ -685,6 +723,19 @@
             }
             pw.decreaseIndent();
         }
+        // Dump repair mode file states
+        final File repairModeFile = getRepairModePersistentDataFile();
+        if (repairModeFile.exists()) {
+            pw.println(TextUtils.formatSimple("Repair Mode [%s]:", repairModeFile.getParent()));
+            pw.increaseIndent();
+            pw.println(TextUtils.formatSimple("%6d %s %s", repairModeFile.length(),
+                    LockSettingsService.timestampToString(repairModeFile.lastModified()),
+                    repairModeFile.getName()));
+            final PersistentData data = readRepairModePersistentData();
+            pw.println(TextUtils.formatSimple("type: %d, user id: %d, payload size: %d",
+                    data.type, data.userId, data.payload != null ? data.payload.length : 0));
+            pw.decreaseIndent();
+        }
     }
 
     static class DatabaseHelper extends SQLiteOpenHelper {
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3d3703a..8e9c21f 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -16,9 +16,14 @@
 
 package com.android.server.locksettings;
 
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
 import static com.android.internal.widget.LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
+import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
+import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityToCredentialType;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -768,11 +773,30 @@
         return PasswordData.fromBytes(passwordData).credentialType;
     }
 
-    static int getFrpCredentialType(byte[] payload) {
-        if (payload == null) {
+    int getSpecialUserCredentialType(int userId) {
+        final PersistentData data = getSpecialUserPersistentData(userId);
+        if (data.type != PersistentData.TYPE_SP_GATEKEEPER
+                && data.type != PersistentData.TYPE_SP_WEAVER) {
+            return CREDENTIAL_TYPE_NONE;
+        }
+        if (data.payload == null) {
             return LockPatternUtils.CREDENTIAL_TYPE_NONE;
         }
-        return PasswordData.fromBytes(payload).credentialType;
+        final int credentialType = PasswordData.fromBytes(data.payload).credentialType;
+        if (credentialType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+            return credentialType;
+        }
+        return pinOrPasswordQualityToCredentialType(data.qualityForUi);
+    }
+
+    private PersistentData getSpecialUserPersistentData(int userId) {
+        if (userId == USER_FRP) {
+            return mStorage.readPersistentDataBlock();
+        }
+        if (userId == USER_REPAIR_MODE) {
+            return mStorage.readRepairModePersistentData();
+        }
+        throw new IllegalArgumentException("Unknown special user id " + userId);
     }
 
     /**
@@ -1057,10 +1081,10 @@
         return sizeOfCredential;
     }
 
-    public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper,
-            LockscreenCredential userCredential,
+    public VerifyCredentialResponse verifySpecialUserCredential(int sourceUserId,
+            IGateKeeperService gatekeeper, LockscreenCredential userCredential,
             ICheckCredentialProgressCallback progressCallback) {
-        PersistentData persistentData = mStorage.readPersistentDataBlock();
+        final PersistentData persistentData = getSpecialUserPersistentData(sourceUserId);
         if (persistentData.type == PersistentData.TYPE_SP_GATEKEEPER) {
             PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
             byte[] stretchedLskf = stretchLskf(userCredential, pwd);
@@ -1071,14 +1095,14 @@
                         0 /* challenge */, pwd.passwordHandle,
                         stretchedLskfToGkPassword(stretchedLskf));
             } catch (RemoteException e) {
-                Slog.e(TAG, "FRP verifyChallenge failed", e);
+                Slog.e(TAG, "Persistent data credential verifyChallenge failed", e);
                 return VerifyCredentialResponse.ERROR;
             }
             return VerifyCredentialResponse.fromGateKeeperResponse(response);
         } else if (persistentData.type == PersistentData.TYPE_SP_WEAVER) {
             final IWeaver weaver = getWeaverService();
             if (weaver == null) {
-                Slog.e(TAG, "No weaver service to verify SP-based FRP credential");
+                Slog.e(TAG, "No weaver service to verify SP-based persistent data credential");
                 return VerifyCredentialResponse.ERROR;
             }
             PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
@@ -1168,6 +1192,57 @@
         }
     }
 
+    /**
+     * Writes the user's synthetic password data to the repair mode file.
+     *
+     * @param protectorId current LSKF based protectorId
+     * @param userId user id of the user
+     */
+    public boolean writeRepairModeCredentialLocked(long protectorId, int userId) {
+        if (!shouldWriteRepairModeCredential(userId)) {
+            return false;
+        }
+        final byte[] data = loadState(PASSWORD_DATA_NAME, protectorId, userId);
+        if (data == null) {
+            Slogf.w(TAG, "Password data not found for user %d", userId);
+            return false;
+        }
+        final PasswordData pwd = PasswordData.fromBytes(data);
+        if (isNoneCredential(pwd)) {
+            Slogf.w(TAG, "User %d has NONE credential", userId);
+            return false;
+        }
+        Slogf.d(TAG, "Writing repair mode credential tied to user %d", userId);
+        final int weaverSlot = loadWeaverSlot(protectorId, userId);
+        if (weaverSlot != INVALID_WEAVER_SLOT) {
+            // write weaver password
+            mStorage.writeRepairModePersistentData(
+                    PersistentData.TYPE_SP_WEAVER, weaverSlot, pwd.toBytes());
+        } else {
+            // write gatekeeper password
+            mStorage.writeRepairModePersistentData(
+                    PersistentData.TYPE_SP_GATEKEEPER, userId, pwd.toBytes());
+        }
+        return true;
+    }
+
+    private boolean shouldWriteRepairModeCredential(int userId) {
+        final UserInfo userInfo = mUserManager.getUserInfo(userId);
+        if (!LockPatternUtils.canUserEnterRepairMode(mContext, userInfo)) {
+            Slogf.w(TAG, "User %d can't enter repair mode", userId);
+            return false;
+        }
+        if (LockPatternUtils.isRepairModeActive(mContext)) {
+            Slog.w(TAG, "Can't write repair mode credential while repair mode is already active");
+            return false;
+        }
+        if (LockPatternUtils.isGsiRunning()) {
+            Slog.w(TAG, "Can't write repair mode credential while GSI is running");
+            return false;
+        }
+        return true;
+    }
+
     private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index ec0d985..77a60289 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -123,7 +123,7 @@
      * @param userId The uid of the user whose profile has been unlocked.
      * @param credentialType The type of credential as defined in {@code LockPatternUtils}
      * @param credential The credential, encoded as a byte array
-     * @param credentialUpdated signals weather credentials were updated.
+     * @param credentialUpdated indicates credentials change.
      * @param platformKeyManager platform key manager
      * @param testOnlyInsecureCertificateHelper utility class used for end-to-end tests
      */
@@ -143,7 +143,7 @@
         mRecoverableKeyStoreDb = recoverableKeyStoreDb;
         mUserId = userId;
         mCredentialType = credentialType;
-        mCredential = credential;
+        mCredential = credential != null ? Arrays.copyOf(credential, credential.length) : null;
         mCredentialUpdated = credentialUpdated;
         mPlatformKeyManager = platformKeyManager;
         mRecoverySnapshotStorage = snapshotStorage;
@@ -160,6 +160,10 @@
             }
         } catch (Exception e) {
             Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
+        } finally {
+            if (mCredential != null) {
+                Arrays.fill(mCredential, (byte) 0); // no longer needed.
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 7009a41..ce3fb85 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -166,6 +166,7 @@
      * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
      * @throws KeyStoreException if there is an error in AndroidKeyStore.
+     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -174,7 +175,7 @@
     @VisibleForTesting
     void regenerate(int userId)
             throws NoSuchAlgorithmException, KeyStoreException, IOException,
-                    RemoteException {
+                    RemoteException, InsecureUserException {
         int generationId = getGenerationId(userId);
         int nextId;
         if (generationId == -1) {
@@ -195,13 +196,14 @@
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
      * @throws IOException if there was an issue with local database update.
+     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
      * @hide
      */
     public PlatformEncryptionKey getEncryptKey(int userId)
             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
-                    IOException, RemoteException {
+                    IOException, RemoteException, InsecureUserException {
         init(userId);
         try {
             // Try to see if the decryption key is still accessible before using the encryption key.
@@ -254,7 +256,7 @@
      */
     public PlatformDecryptionKey getDecryptKey(int userId)
             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
-                    IOException, RemoteException {
+                    IOException, InsecureUserException, RemoteException {
         init(userId);
         try {
             PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
@@ -328,7 +330,7 @@
      */
     void init(int userId)
             throws KeyStoreException, NoSuchAlgorithmException, IOException,
-                    RemoteException {
+                    RemoteException, InsecureUserException {
         int generationId = getGenerationId(userId);
         if (isKeyLoaded(userId, generationId)) {
             Log.i(TAG, String.format(
@@ -414,7 +416,8 @@
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      */
     private void generateAndLoadKey(int userId, int generationId)
-            throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException {
+            throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException,
+                InsecureUserException {
         String encryptAlias = getEncryptAlias(userId, generationId);
         String decryptAlias = getDecryptAlias(userId, generationId);
         // SecretKey implementation doesn't provide reliable way to destroy the secret
@@ -427,23 +430,31 @@
                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
         // Skip UserAuthenticationRequired for main user
         if (userId ==  UserHandle.USER_SYSTEM) {
+            // attempt to store key will fail if screenlock is not set.
             decryptionKeyProtection.setUnlockedDeviceRequired(true);
         } else {
             // Don't set protection params to prevent losing key.
         }
         // Store decryption key first since it is more likely to fail.
-        mKeyStore.setEntry(
-                decryptAlias,
-                new KeyStore.SecretKeyEntry(secretKey),
-                decryptionKeyProtection.build());
-        mKeyStore.setEntry(
-                encryptAlias,
-                new KeyStore.SecretKeyEntry(secretKey),
-                new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
-                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                    .build());
-
+        try {
+            mKeyStore.setEntry(
+                    decryptAlias,
+                    new KeyStore.SecretKeyEntry(secretKey),
+                    decryptionKeyProtection.build());
+            mKeyStore.setEntry(
+                    encryptAlias,
+                    new KeyStore.SecretKeyEntry(secretKey),
+                    new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
+                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                        .build());
+        } catch (KeyStoreException e) {
+            if (!isDeviceSecure(userId)) {
+                throw new InsecureUserException("Screenlock is not set");
+            } else {
+                throw e;
+            }
+        }
         setGenerationId(userId, generationId);
     }
 
@@ -477,4 +488,8 @@
         return keyStore;
     }
 
+    private boolean isDeviceSecure(int userId) {
+        return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
+    }
+
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 24dbce4..10b6052 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -19,6 +19,7 @@
 import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
 import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
 import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
+import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
 import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
@@ -194,8 +195,12 @@
         mApplicationKeyStorage = applicationKeyStorage;
         mTestCertHelper = testOnlyInsecureCertificateHelper;
         mCleanupManager = cleanupManager;
-        // Clears data for removed users.
-        mCleanupManager.verifyKnownUsers();
+        try {
+            // Clears data for removed users.
+            mCleanupManager.verifyKnownUsers();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to verify known users", e);
+        }
         try {
             mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
         } catch (NoSuchAlgorithmException e) {
@@ -750,6 +755,8 @@
             throw new RuntimeException(e);
         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+        } catch (InsecureUserException e) {
+            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
         }
 
         try {
@@ -817,6 +824,8 @@
             throw new RuntimeException(e);
         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+        } catch (InsecureUserException e) {
+            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
         }
 
         try {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
index 06db6b8..ef56a1e 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
@@ -138,7 +138,7 @@
         } catch (android.security.KeyStoreException e) {
             if (e.getNumericErrorCode()
                     == android.security.KeyStoreException.ERROR_KEY_DOES_NOT_EXIST) {
-                Log.e(TAG, "Failed to get grant for KeyStore key - key not found", e);
+                Log.w(TAG, "Failed to get grant for KeyStore key - key not found");
                 throw new ServiceSpecificException(ERROR_KEY_NOT_FOUND, e.getMessage());
             }
             Log.e(TAG, "Failed to get grant for KeyStore key.", e);
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 36adea7..392b86e 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -75,7 +75,6 @@
 import com.android.internal.util.HexDump;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.NetdUtils;
-import com.android.net.module.util.NetdUtils.ModifyOperation;
 import com.android.net.module.util.PermissionUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -758,18 +757,6 @@
         }
     }
 
-    @Override
-    public void addRoute(int netId, RouteInfo route) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        NetdUtils.modifyRoute(mNetdService, ModifyOperation.ADD, netId, route);
-    }
-
-    @Override
-    public void removeRoute(int netId, RouteInfo route) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
-        NetdUtils.modifyRoute(mNetdService, ModifyOperation.REMOVE, netId, route);
-    }
-
     @android.annotation.EnforcePermission(android.Manifest.permission.SHUTDOWN)
     @Override
     public void shutdown() {
@@ -1072,17 +1059,25 @@
                 Log.w(TAG, "setDataSaverMode(): already " + mDataSaverMode);
                 return true;
             }
-            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "bandwidthEnableDataSaver");
+            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setDataSaverModeEnabled");
             try {
-                final boolean changed = mNetdService.bandwidthEnableDataSaver(enable);
-                if (changed) {
+                if (SdkLevel.isAtLeastV()) {
+                    // setDataSaverEnabled throws if it fails to set data saver.
+                    mContext.getSystemService(ConnectivityManager.class)
+                            .setDataSaverEnabled(enable);
                     mDataSaverMode = enable;
+                    return true;
                 } else {
-                    Log.w(TAG, "setDataSaverMode(" + enable + "): netd command silently failed");
+                    final boolean changed = mNetdService.bandwidthEnableDataSaver(enable);
+                    if (changed) {
+                        mDataSaverMode = enable;
+                    } else {
+                        Log.e(TAG, "setDataSaverMode(" + enable + "): failed to set iptables");
+                    }
+                    return changed;
                 }
-                return changed;
-            } catch (RemoteException e) {
-                Log.w(TAG, "setDataSaverMode(" + enable + "): netd command failed", e);
+            } catch (RemoteException | IllegalStateException e) {
+                Log.e(TAG, "setDataSaverMode(" + enable + "): failed with exception", e);
                 return false;
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS
index 6c4dd6d..9f16662 100644
--- a/services/core/java/com/android/server/notification/OWNERS
+++ b/services/core/java/com/android/server/notification/OWNERS
@@ -1,6 +1,9 @@
-# Bug component: 34005
+# Bug component: 78010
 
 juliacr@google.com
 yurilin@google.com
+aroederer@google.com
+matiashe@google.com
+valiiftime@google.com
 jeffdq@google.com
 dsandler@android.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index f95f7bc..0803bb6 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -122,7 +122,7 @@
                 StorageManagerInternal.class);
         for (UserInfo user : umInternal.getUsers(false /*excludeDying*/)) {
             final int flags;
-            if (StorageManager.isUserKeyUnlocked(user.id)
+            if (StorageManager.isCeStorageUnlocked(user.id)
                     && smInternal.isCeStoragePrepared(user.id)) {
                 flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
             } else if (umInternal.isUserRunning(user.id)) {
@@ -404,7 +404,7 @@
         // First look for stale data that doesn't belong, and check if things
         // have changed since we did our last restorecon
         if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
-            if (StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId)) {
+            if (StorageManager.isFileEncrypted() && !StorageManager.isCeStorageUnlocked(userId)) {
                 throw new RuntimeException(
                         "Yikes, someone asked us to reconcile CE storage while " + userId
                                 + " was still locked; this would have caused massive data loss!");
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 78f1fa6..723a3ae6 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -3538,7 +3538,7 @@
     @Override
     public int getPackageStartability(boolean safeMode, @NonNull String packageName, int callingUid,
             @UserIdInt int userId) {
-        final boolean userKeyUnlocked = StorageManager.isUserKeyUnlocked(userId);
+        final boolean ceStorageUnlocked = StorageManager.isCeStorageUnlocked(userId);
         final PackageStateInternal ps = getPackageStateInternal(packageName);
         if (ps == null || shouldFilterApplication(ps, callingUid, userId)
                 || !ps.getUserStateOrDefault(userId).isInstalled()) {
@@ -3553,7 +3553,7 @@
             return PackageManagerService.PACKAGE_STARTABILITY_FROZEN;
         }
 
-        if (!userKeyUnlocked && !AndroidPackageUtils.isEncryptionAware(ps.getPkg())) {
+        if (!ceStorageUnlocked && !AndroidPackageUtils.isEncryptionAware(ps.getPkg())) {
             return PackageManagerService.PACKAGE_STARTABILITY_DIRECT_BOOT_UNSUPPORTED;
         }
         return PackageManagerService.PACKAGE_STARTABILITY_OK;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 622cb66..988a32f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -114,6 +114,7 @@
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.DataLoaderType;
+import android.content.pm.Flags;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
@@ -167,6 +168,7 @@
 import com.android.server.EventLogTags;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.SystemConfig;
+import com.android.server.art.model.ArtFlags;
 import com.android.server.art.model.DexoptParams;
 import com.android.server.art.model.DexoptResult;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
@@ -484,20 +486,30 @@
             pkgSetting.setLoadingProgress(1f);
         }
 
+        // TODO: passes the package name as an argument in a message to the handler for V+
+        //  so we don't need to rely on creating lambda objects so frequently.
+        if (UpdateOwnershipHelper.hasValidOwnershipDenyList(pkgSetting)) {
+            mPm.mHandler.post(() -> handleUpdateOwnerDenyList(pkgSetting));
+        }
+        return pkg;
+    }
+
+    private void handleUpdateOwnerDenyList(PackageSetting pkgSetting) {
         ArraySet<String> listItems = mUpdateOwnershipHelper.readUpdateOwnerDenyList(pkgSetting);
         if (listItems != null && !listItems.isEmpty()) {
-            mUpdateOwnershipHelper.addToUpdateOwnerDenyList(pkgSetting.getPackageName(), listItems);
-            for (String unownedPackage : listItems) {
-                PackageSetting unownedSetting = mPm.mSettings.getPackageLPr(unownedPackage);
-                SystemConfig config = SystemConfig.getInstance();
-                if (unownedSetting != null
-                        && config.getSystemAppUpdateOwnerPackageName(unownedPackage) == null) {
-                    unownedSetting.setUpdateOwnerPackage(null);
+            mUpdateOwnershipHelper.addToUpdateOwnerDenyList(pkgSetting.getPackageName(),
+                    listItems);
+            SystemConfig config = SystemConfig.getInstance();
+            synchronized (mPm.mLock) {
+                for (String unownedPackage : listItems) {
+                    PackageSetting unownedSetting = mPm.mSettings.getPackageLPr(unownedPackage);
+                    if (unownedSetting != null
+                            && config.getSystemAppUpdateOwnerPackageName(unownedPackage) == null) {
+                        unownedSetting.setUpdateOwnerPackage(null);
+                    }
                 }
             }
         }
-
-        return pkg;
     }
 
     /**
@@ -2534,8 +2546,15 @@
                             LocalManagerRegistry.getManager(PackageManagerLocal.class);
                     try (PackageManagerLocal.FilteredSnapshot snapshot =
                                     packageManagerLocal.withFilteredSnapshot()) {
-                        DexoptParams params =
-                                dexoptOptions.convertToDexoptParams(0 /* extraFlags */);
+                        boolean ignoreDexoptProfile =
+                                (installRequest.getInstallFlags()
+                                        & PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE)
+                                != 0;
+                        /*@DexoptFlags*/ int extraFlags =
+                                ignoreDexoptProfile && Flags.useArtServiceV2()
+                                ? ArtFlags.FLAG_IGNORE_PROFILE
+                                : 0;
+                        DexoptParams params = dexoptOptions.convertToDexoptParams(extraFlags);
                         DexoptResult dexOptResult = DexOptHelper.getArtManagerLocal().dexoptPackage(
                                 snapshot, packageName, params);
                         installRequest.onDexoptFinished(dexOptResult);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 9ac983d..c401dab 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -24,6 +24,7 @@
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.PackageStats;
+import android.os.Binder;
 import android.os.Build;
 import android.os.CreateAppDataArgs;
 import android.os.CreateAppDataResult;
@@ -34,9 +35,11 @@
 import android.os.ServiceManager;
 import android.os.storage.CrateMetadata;
 import android.text.format.DateUtils;
+import android.util.EventLog;
 import android.util.Slog;
 
 import com.android.internal.os.BackgroundThread;
+import com.android.server.EventLogTags;
 import com.android.server.SystemService;
 
 import dalvik.system.BlockGuard;
@@ -438,6 +441,26 @@
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.clearAppData(uuid, packageName, userId, flags, ceDataInode);
+
+            final StackTraceElement[] elements = Thread.currentThread().getStackTrace();
+            String className;
+            String methodName;
+            String fileName;
+            int lineNumber;
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            EventLog.writeEvent(EventLogTags.INSTALLER_CLEAR_APP_DATA_CALLER, pid, uid, packageName,
+                    flags);
+            // Skip the first two elements since they are always the same, ie
+            // Thread#getStackTrace() and VMStack#getThreadStackTrace()
+            for (int i = 2; i < elements.length; i++) {
+                className = elements[i].getClassName();
+                methodName = elements[i].getMethodName();
+                fileName = elements[i].getFileName();
+                lineNumber = elements[i].getLineNumber();
+                EventLog.writeEvent(EventLogTags.INSTALLER_CLEAR_APP_DATA_CALL_STACK, methodName,
+                        className, fileName, lineNumber);
+            }
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 481bd97..4780ed1 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -89,6 +89,21 @@
         if (packageState == null || packageState.getPkg() == null) {
             throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
         }
+        final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState,
+                mPm.mUserManager.getUserIds(), true);
+        final UserHandle userForMove;
+        if (installedUserIds.length > 0) {
+            userForMove = UserHandle.of(installedUserIds[0]);
+        } else {
+            throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST,
+                    "Package is not installed for any user");
+        }
+        for (int userId : installedUserIds) {
+            if (snapshot.shouldFilterApplicationIncludingUninstalled(packageState, callingUid,
+                    userId)) {
+                throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
+            }
+        }
         final AndroidPackage pkg = packageState.getPkg();
         if (packageState.isSystem()) {
             throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE,
@@ -137,8 +152,6 @@
         final String label = String.valueOf(pm.getApplicationLabel(
                 AndroidPackageUtils.generateAppInfoWithoutState(pkg)));
         final int targetSdkVersion = pkg.getTargetSdkVersion();
-        final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState,
-                mPm.mUserManager.getUserIds(), true);
         final String fromCodePath;
         if (codeFile.getParentFile().getName().startsWith(
                 PackageManagerService.RANDOM_DIR_PREFIX)) {
@@ -185,7 +198,8 @@
         // If we're moving app data around, we need all the users unlocked
         if (moveCompleteApp) {
             for (int userId : installedUserIds) {
-                if (StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId)) {
+                if (StorageManager.isFileEncrypted()
+                        && !StorageManager.isCeStorageUnlocked(userId)) {
                     freezer.close();
                     throw new PackageManagerException(MOVE_FAILED_LOCKED_USER,
                             "User " + userId + " must be unlocked");
@@ -305,8 +319,8 @@
         final PackageLite lite = ret.isSuccess() ? ret.getResult() : null;
         final InstallingSession installingSession = new InstallingSession(origin, move,
                 installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource,
-                volumeUuid, user, packageAbiOverride,  PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED,
-                lite, mPm);
+                volumeUuid, userForMove, packageAbiOverride,
+                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm);
         installingSession.movePackage();
     }
 
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 6a2ddc8..ea082cf 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -159,6 +159,9 @@
             if (pkgSetting.getPkg().isCoreApp()) {
                 throw new IllegalStateException("Found a core app that's not important");
             }
+            // Use REASON_FIRST_BOOT to query "pm.dexopt.first-boot" for the compiler filter, but
+            // the reason itself won't make it into the actual compiler reason because it will be
+            // overridden in otapreopt.cpp.
             mDexoptCommands.addAll(generatePackageDexopts(pkgSetting.getPkg(), pkgSetting,
                     PackageManagerService.REASON_FIRST_BOOT));
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 20bd56d..a1c435a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3460,7 +3460,7 @@
         UserManagerInternal umInternal = mInjector.getUserManagerInternal();
         StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
         final int flags;
-        if (StorageManager.isUserKeyUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) {
+        if (StorageManager.isCeStorageUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) {
             flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
         } else if (umInternal.isUserRunning(userId)) {
             flags = StorageManager.FLAG_STORAGE_DE;
@@ -4729,6 +4729,9 @@
                 throw new SecurityException("Cannot clear data for a protected package: "
                         + packageName);
             }
+            final int callingPid = Binder.getCallingPid();
+            EventLog.writeEvent(EventLogTags.PM_CLEAR_APP_DATA_CALLER, callingPid, callingUid,
+                    packageName);
 
             // Queue up an async operation since the package deletion may take a little while.
             mHandler.post(new Runnable() {
@@ -4861,6 +4864,9 @@
                     /* checkShell= */ false, "delete application cache files");
             final int hasAccessInstantApps = mContext.checkCallingOrSelfPermission(
                     android.Manifest.permission.ACCESS_INSTANT_APPS);
+            final int callingPid = Binder.getCallingPid();
+            EventLog.writeEvent(EventLogTags.PM_CLEAR_APP_DATA_CALLER, callingPid, callingUid,
+                    packageName);
 
             // Queue up an async operation since the package deletion may take a little while.
             mHandler.post(() -> {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 2327b85..3a84e15 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3368,6 +3368,9 @@
                     sessionParams.installFlags |=
                             PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
                     break;
+                case "--ignore-dexopt-profile":
+                    sessionParams.installFlags |= PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE;
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown option " + opt);
             }
@@ -4259,7 +4262,7 @@
         pw.println("       [--enable-rollback]");
         pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
         pw.println("       [--apex] [--non-staged] [--force-non-staged]");
-        pw.println("       [--staged-ready-timeout TIMEOUT]");
+        pw.println("       [--staged-ready-timeout TIMEOUT] [--ignore-dexopt-profile]");
         pw.println("       [PATH [SPLIT...]|-]");
         pw.println("    Install an application.  Must provide the apk data to install, either as");
         pw.println("    file path(s) or '-' to read from stdin.  Options are:");
@@ -4299,6 +4302,13 @@
         pw.println("          milliseconds for pre-reboot verification to complete when");
         pw.println("          performing staged install. This flag is used to alter the waiting");
         pw.println("          time. You can skip the waiting time by specifying a TIMEOUT of '0'");
+        pw.println("      --ignore-dexopt-profile: If set, all profiles are ignored by dexopt");
+        pw.println("          during the installation, including the profile in the DM file and");
+        pw.println("          the profile embedded in the APK file. If an invalid profile is");
+        pw.println("          provided during installation, no warning will be reported by `adb");
+        pw.println("          install`.");
+        pw.println("          This option does not affect later dexopt operations (e.g.,");
+        pw.println("          background dexopt and manual `pm compile` invocations).");
         pw.println("");
         pw.println("  install-existing [--user USER_ID|all|current]");
         pw.println("       [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE");
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 8f8f437..8961923 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -183,7 +183,7 @@
                 StorageManagerInternal.class);
         for (UserInfo user : mPm.mUserManager.getUsers(false /* includeDying */)) {
             final int flags;
-            if (StorageManager.isUserKeyUnlocked(user.id)
+            if (StorageManager.isCeStorageUnlocked(user.id)
                     && smInternal.isCeStoragePrepared(user.id)) {
                 flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
             } else if (umInternal.isUserRunning(user.id)) {
diff --git a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java
index 43752f3..adac68b 100644
--- a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java
+++ b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java
@@ -48,7 +48,7 @@
 
     private final Object mLock = new Object();
 
-    private static boolean hasValidOwnershipDenyList(PackageSetting pkgSetting) {
+    static boolean hasValidOwnershipDenyList(PackageSetting pkgSetting) {
         AndroidPackage pkg = pkgSetting.getPkg();
         // we're checking for uses-permission for these priv permissions instead of grant as we're
         // only considering system apps to begin with, so presumed to be granted.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7e88e13..e11e226 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1348,7 +1348,7 @@
             }
             final boolean needToShowConfirmCredential = !dontAskCredential
                     && mLockPatternUtils.isSecure(userId)
-                    && (!hasUnifiedChallenge || !StorageManager.isUserKeyUnlocked(userId));
+                    && (!hasUnifiedChallenge || !StorageManager.isCeStorageUnlocked(userId));
             if (needToShowConfirmCredential) {
                 if (onlyIfCredentialNotRequired) {
                     return false;
@@ -4905,9 +4905,9 @@
                 }
             }
 
-            t.traceBegin("createUserKey");
+            t.traceBegin("createUserStorageKeys");
             final StorageManager storage = mContext.getSystemService(StorageManager.class);
-            storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
+            storage.createUserStorageKeys(userId, userInfo.serialNumber, userInfo.isEphemeral());
             t.traceEnd();
 
             // Only prepare DE storage here.  CE storage will be prepared later, when the user is
@@ -5731,17 +5731,18 @@
     private void removeUserState(final @UserIdInt int userId) {
         Slog.i(LOG_TAG, "Removing user state of user " + userId);
 
-        // Cleanup lock settings.  This must happen before destroyUserKey(), since the user's DE
-        // storage must still be accessible for the lock settings state to be properly cleaned up.
+        // Cleanup lock settings.  This requires that the user's DE storage still be accessible, so
+        // this must happen before destroyUserStorageKeys().
         mLockPatternUtils.removeUser(userId);
 
         // Evict and destroy the user's CE and DE encryption keys.  At this point, the user's CE and
         // DE storage is made inaccessible, except to delete its contents.
         try {
-            mContext.getSystemService(StorageManager.class).destroyUserKey(userId);
+            mContext.getSystemService(StorageManager.class).destroyUserStorageKeys(userId);
         } catch (IllegalStateException e) {
             // This may be simply because the user was partially created.
-            Slog.i(LOG_TAG, "Destroying key for user " + userId + " failed, continuing anyway", e);
+            Slog.i(LOG_TAG, "Destroying storage keys for user " + userId
+                    + " failed, continuing anyway", e);
         }
 
         // Cleanup package manager settings
@@ -6991,9 +6992,9 @@
             synchronized (mUserStates) {
                 state = mUserStates.get(userId, -1);
             }
-            // Special case, in the stopping/shutdown state user key can still be unlocked
+            // Special case: in the stopping/shutdown state, CE storage can still be unlocked.
             if (state == UserState.STATE_STOPPING || state == UserState.STATE_SHUTDOWN) {
-                return StorageManager.isUserKeyUnlocked(userId);
+                return StorageManager.isCeStorageUnlocked(userId);
             }
             return (state == UserState.STATE_RUNNING_UNLOCKING)
                     || (state == UserState.STATE_RUNNING_UNLOCKED);
@@ -7010,9 +7011,9 @@
             synchronized (mUserStates) {
                 state = mUserStates.get(userId, -1);
             }
-            // Special case, in the stopping/shutdown state user key can still be unlocked
+            // Special case: in the stopping/shutdown state, CE storage can still be unlocked.
             if (state == UserState.STATE_STOPPING || state == UserState.STATE_SHUTDOWN) {
-                return StorageManager.isUserKeyUnlocked(userId);
+                return StorageManager.isCeStorageUnlocked(userId);
             }
             return state == UserState.STATE_RUNNING_UNLOCKED;
         }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 297ad73..c24d523 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1001,7 +1001,9 @@
             }
 
             synchronized (mLock) {
-                mAttributions.put(source.getToken(), source);
+                // Change the token for the AttributionSource we're storing, so that we don't store
+                // a strong reference to the original token inside the map itself.
+                mAttributions.put(source.getToken(), source.withDefaultToken());
             }
         }
 
@@ -1009,7 +1011,7 @@
             synchronized (mLock) {
                 final AttributionSource cachedSource = mAttributions.get(source.getToken());
                 if (cachedSource != null) {
-                    return cachedSource.equals(source);
+                    return cachedSource.equalsExceptToken(source);
                 }
                 return false;
             }
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 0e99e7e..bd9738e 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -1315,7 +1315,8 @@
         }
 
         private boolean isTaskStartedFromLauncher(String currPkg, TaskInfo taskInfo) {
-            return currPkg.equals(taskInfo.baseActivity.getPackageName())
+            return taskInfo.baseActivity != null
+                    && currPkg.equals(taskInfo.baseActivity.getPackageName())
                     && isLauncherIntent(taskInfo.baseIntent);
         }
 
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index a0e91ad..1970ee4 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -3,3 +3,5 @@
 philipjunker@google.com
 
 per-file ThermalManagerService.java=wvw@google.com
+per-file LowPowerStandbyController.java=qingxun@google.com
+per-file LowPowerStandbyControllerInternal.java=qingxun@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index 7b0fe9a..a01bac6 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -266,10 +266,10 @@
     }
 
     /**
-     * @return {@code true} iff. {@code userId} is locked on an FBE device.
+     * @return {@code true} iff the credential-encrypted storage for {@code userId} is locked.
      */
     @VisibleForTesting
     public boolean isUserCredentialLocked(int userId) {
-        return StorageManager.isFileEncrypted() && !StorageManager.isUserKeyUnlocked(userId);
+        return StorageManager.isFileEncrypted() && !StorageManager.isCeStorageUnlocked(userId);
     }
 }
diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS
index 174ad3a..c33f3d9 100644
--- a/services/core/java/com/android/server/stats/OWNERS
+++ b/services/core/java/com/android/server/stats/OWNERS
@@ -1,11 +1,10 @@
 jeffreyhuang@google.com
 joeo@google.com
-jtnguyen@google.com
+monicamwang@google.com
 muhammadq@google.com
+rayhdez@google.com
 rslawik@google.com
-ruchirr@google.com
 sharaienko@google.com
 singhtejinder@google.com
 tsaichristine@google.com
 yaochen@google.com
-yro@google.com
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 9128974..76ee845 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1281,12 +1281,19 @@
 
     private void addBytesTransferByTagAndMeteredAtoms(@NonNull NetworkStatsExt statsExt,
             @NonNull List<StatsEvent> pulledData) {
+        // Workaround for 5G NSA mode, see {@link NetworkStatsManager#NETWORK_TYPE_5G_NSA}.
+        // 5G NSA mode means the primary cell is LTE with a secondary connection to an
+        // NR cell. To mitigate risk, NetworkStats is currently storing this state as
+        // a fake RAT type rather than storing the boolean separately.
+        final boolean is5GNsa = statsExt.ratType == NetworkStatsManager.NETWORK_TYPE_5G_NSA;
+
         for (NetworkStats.Entry entry : statsExt.stats) {
             pulledData.add(FrameworkStatsLog.buildStatsEvent(
                     FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED, entry.getUid(),
                     entry.getMetered() == NetworkStats.METERED_YES, entry.getTag(),
                     entry.getRxBytes(), entry.getRxPackets(), entry.getTxBytes(),
-                    entry.getTxPackets()));
+                    entry.getTxPackets(),
+                    is5GNsa ? TelephonyManager.NETWORK_TYPE_LTE : statsExt.ratType));
         }
     }
 
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index d958222..9213d96 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.net.vcn.FeatureFlags;
+import android.net.vcn.FeatureFlagsImpl;
 import android.os.Looper;
 
 import java.util.Objects;
@@ -31,6 +33,7 @@
     @NonNull private final Context mContext;
     @NonNull private final Looper mLooper;
     @NonNull private final VcnNetworkProvider mVcnNetworkProvider;
+    @NonNull private final FeatureFlags mFeatureFlags;
     private final boolean mIsInTestMode;
 
     public VcnContext(
@@ -42,6 +45,9 @@
         mLooper = Objects.requireNonNull(looper, "Missing looper");
         mVcnNetworkProvider = Objects.requireNonNull(vcnNetworkProvider, "Missing networkProvider");
         mIsInTestMode = isInTestMode;
+
+        // Auto-generated class
+        mFeatureFlags = new FeatureFlagsImpl();
     }
 
     @NonNull
@@ -63,6 +69,11 @@
         return mIsInTestMode;
     }
 
+    @NonNull
+    public FeatureFlags getFeatureFlags() {
+        return mFeatureFlags;
+    }
+
     /**
      * 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 d480ddb..54c97dd 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1222,6 +1222,14 @@
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     void setSafeModeAlarm() {
+        final boolean isFlagSafeModeConfigEnabled = mVcnContext.getFeatureFlags().safeModeConfig();
+        logVdbg("isFlagSafeModeConfigEnabled " + isFlagSafeModeConfigEnabled);
+
+        if (isFlagSafeModeConfigEnabled && !mConnectionConfig.isSafeModeEnabled()) {
+            logVdbg("setSafeModeAlarm: safe mode disabled");
+            return;
+        }
+
         logVdbg("Setting safe mode alarm; mCurrentToken: " + mCurrentToken);
 
         // Only schedule a NEW alarm if none is already set.
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 9cf0834..749cd8e 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -731,18 +731,24 @@
         private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
 
         public boolean isUidForeground(int uid) {
-            return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
-                    <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            synchronized (this) {
+                return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+                        <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            }
         }
 
         @Override
         public void onUidGone(int uid, boolean disabled) {
-            mProcStatesCache.delete(uid);
+            synchronized (this) {
+                mProcStatesCache.delete(uid);
+            }
         }
 
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
-            mProcStatesCache.put(uid, procState);
+            synchronized (this) {
+                mProcStatesCache.put(uid, procState);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 18a6254..82a954d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4214,6 +4214,9 @@
                 Slog.v(TAG_APP, "Keeping entry during removeHistory for activity " + this);
             }
         }
+        if (task != null && task.mKillProcessesOnDestroyed) {
+            mTaskSupervisor.removeTimeoutOfKillProcessesOnProcessDied(this, task);
+        }
         // upgrade transition trigger to task if this is the last activity since it means we are
         // closing the task.
         final WindowContainer trigger = remove && task != null && task.getChildCount() == 1
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index b34ae19..ecdca93 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1891,7 +1891,7 @@
         // DestroyActivityItem may be called first.
         final ActivityRecord top = task.getTopMostActivity();
         if (top != null && top.finishing && !top.mAppStopped && top.lastVisibleTime > 0
-                && !task.mKillProcessesOnDestroyed) {
+                && !task.mKillProcessesOnDestroyed && top.hasProcess()) {
             task.mKillProcessesOnDestroyed = true;
             mHandler.sendMessageDelayed(
                     mHandler.obtainMessage(KILL_TASK_PROCESSES_TIMEOUT_MSG, task),
@@ -1901,8 +1901,26 @@
         killTaskProcessesIfPossible(task);
     }
 
+    void removeTimeoutOfKillProcessesOnProcessDied(@NonNull ActivityRecord r, @NonNull Task task) {
+        if (r.packageName.equals(task.getBasePackageName())) {
+            task.mKillProcessesOnDestroyed = false;
+            mHandler.removeMessages(KILL_TASK_PROCESSES_TIMEOUT_MSG, task);
+        }
+    }
+
     void killTaskProcessesOnDestroyedIfNeeded(Task task) {
         if (task == null || !task.mKillProcessesOnDestroyed) return;
+        final int[] numDestroyingActivities = new int[1];
+        task.forAllActivities(r ->  {
+            if (r.finishing && r.lastVisibleTime > 0 && r.attachedToProcess()) {
+                numDestroyingActivities[0]++;
+            }
+        });
+        if (numDestroyingActivities[0] > 1) {
+            // Skip if there are still destroying activities. When the last activity reports
+            // destroyed, the number will be 1 to proceed the kill.
+            return;
+        }
         mHandler.removeMessages(KILL_TASK_PROCESSES_TIMEOUT_MSG, task);
         killTaskProcessesIfPossible(task);
     }
@@ -2763,7 +2781,7 @@
                 } break;
                 case KILL_TASK_PROCESSES_TIMEOUT_MSG: {
                     final Task task = (Task) msg.obj;
-                    if (task.mKillProcessesOnDestroyed) {
+                    if (task.mKillProcessesOnDestroyed && task.hasActivity()) {
                         Slog.i(TAG, "Destroy timeout of remove-task, attempt to kill " + task);
                         killTaskProcessesIfPossible(task);
                     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 7addaa2..1c8770b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4668,6 +4668,17 @@
             scheduleAnimation();
 
             mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged());
+        } else if (mImeControlTarget != null && mImeControlTarget == mImeLayeringTarget) {
+            // Even if the IME surface parent is not changed, the layer target belonging to the
+            // parent may have changes. Then attempt to reassign if the IME control target is
+            // possible to be the relative layer.
+            final SurfaceControl lastRelativeLayer = mImeWindowsContainer.getLastRelativeLayer();
+            if (lastRelativeLayer != mImeLayeringTarget.mSurfaceControl) {
+                assignRelativeLayerForIme(getSyncTransaction(), false /* forceUpdate */);
+                if (lastRelativeLayer != mImeWindowsContainer.getLastRelativeLayer()) {
+                    scheduleAnimation();
+                }
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index f6c3640..cd70447 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -16,6 +16,10 @@
 mariiasand@google.com
 rgl@google.com
 yunfanc@google.com
+wilsonshih@google.com
 
-per-file BackgroundActivityStartController.java = set noparent
-per-file BackgroundActivityStartController.java = brufino@google.com, topjohnwu@google.com, achim@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
+# Files related to background activity launches
+per-file Background*Start* = set noparent
+per-file Background*Start* = file:/BAL_OWNERS
+per-file Background*Start* = ogunwale@google.com, louischang@google.com
+
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 9a0e47d..eba9bf6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1637,8 +1637,8 @@
             return false;
         }
 
-        if (!StorageManager.isUserKeyUnlocked(mCurrentUser)) {
-            // Can't launch home on secondary display areas if device is still locked.
+        if (!StorageManager.isCeStorageUnlocked(mCurrentUser)) {
+            // Can't launch home on secondary display areas if CE storage is still locked.
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3063d46..6d560e4 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4626,7 +4626,7 @@
                                 // Expanding pip into new rotation, so create a rotation leash
                                 // until the display is rotated.
                                 topActivity.getOrCreateFixedRotationLeash(
-                                        topActivity.getSyncTransaction());
+                                        topActivity.getPendingTransaction());
                             }
                             lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask");
                         }
@@ -6510,11 +6510,11 @@
                 mActivityType = ACTIVITY_TYPE_STANDARD;
             }
 
-            if (mActivityType != ACTIVITY_TYPE_STANDARD
+            if (!DisplayContent.alwaysCreateRootTask(tda.getWindowingMode(), mActivityType)
                     && mActivityType != ACTIVITY_TYPE_UNDEFINED) {
-                // For now there can be only one root task of a particular non-standard activity
-                // type on a display. So, get that ignoring whatever windowing mode it is
-                // currently in.
+                // Only Recents or Standard activity types are allowed to have more than one
+                // root task on a display, this is independent of whatever windowing mode it
+                // is currently in.
                 Task rootTask = tda.getRootTask(WINDOWING_MODE_UNDEFINED, mActivityType);
                 if (rootTask != null) {
                     throw new IllegalArgumentException("Root task=" + rootTask + " of activityType="
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0152666..4cab3a2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2635,6 +2635,10 @@
         return mLastLayer;
     }
 
+    SurfaceControl getLastRelativeLayer() {
+        return mLastRelativeToLayer;
+    }
+
     protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
         if (mSurfaceFreezer.hasLeash()) {
             // When the freezer has created animation leash parent for the window, set the layer
diff --git a/services/midi/OWNERS b/services/midi/OWNERS
index f4d51f9..683cae1 100644
--- a/services/midi/OWNERS
+++ b/services/midi/OWNERS
@@ -1 +1,3 @@
 philburk@google.com
+robertwu@google.com
+elaurent@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/proguard.flags b/services/proguard.flags
index e11e613..84f2a52 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -14,13 +14,20 @@
 }
 
 # APIs referenced by dependent JAR files and modules
--keep @interface android.annotation.SystemApi
+# TODO(b/300514883): Pull @SystemApi keep rules from system-api.pro.
+-keep interface android.annotation.SystemApi
 -keep @android.annotation.SystemApi class * {
   public protected *;
 }
 -keepclasseswithmembers class * {
   @android.annotation.SystemApi *;
 }
+# Also ensure nested classes are kept. This is overly conservative, but handles
+# cases where such classes aren't explicitly marked @SystemApi.
+-if @android.annotation.SystemApi class *
+-keep public class <1>$** {
+  public protected *;
+}
 
 # Derivatives of SystemService and other services created via reflection
 -keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService {
@@ -38,10 +45,6 @@
    public static void write(...);
 }
 
-# Binder interfaces
--keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface
--keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface
-
 # Various classes subclassed in or referenced via JNI in ethernet-service
 -keep public class android.net.** { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index c6a914b..ccbaf78 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -2530,7 +2530,7 @@
                 PROCESS_STATE_NONEXISTENT, PROCESS_STATE_NONEXISTENT,
                 0, 0, false, false, false, ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE,
                 false, false, false, hasShownUi, false, false, false, false, false, false, null,
-                0, 0, 0, true, 0, null, false);
+                0, Long.MIN_VALUE, Long.MIN_VALUE, true, 0, null, false);
     }
 
     private ProcessRecord makeProcessRecord(ActivityManagerService service, int pid, int uid,
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index dc1c6d5..74d664f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -75,7 +75,7 @@
     private static final String TEST_PACKAGE = "package1";
     private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE };
     private static final String TEST_TRANSPORT = "transport";
-    private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 1;
+    private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 100;
 
     @Mock Context mContext;
     @Mock IBackupManagerMonitor mBackupManagerMonitor;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 7cd8819..d687915 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -320,11 +320,20 @@
     @Test
     public void testGetBootUser_Headless_ThrowsIfOnlySystemUserExists() throws Exception {
         setSystemUserHeadless(true);
+        removeNonSystemUsers();
 
         assertThrows(UserManager.CheckedUserOperationException.class,
                 () -> mUmi.getBootUser(/* waitUntilSet= */ false));
     }
 
+    private void removeNonSystemUsers() {
+        for (UserInfo user : mUms.getUsers(true)) {
+            if (!user.getUserHandle().isSystem()) {
+                mUms.removeUserInfo(user.id);
+            }
+        }
+    }
+
     private void mockCurrentUser(@UserIdInt int userId) {
         mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
 
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index f770f8c..e656cf3 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -65,6 +65,7 @@
         "ActivityContext",
         "coretests-aidl",
         "securebox",
+        "flag-junit",
     ],
 
     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 dccacb4..a3403bb 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -345,7 +345,7 @@
         assertWithMessage("should not have received intents")
                 .that(getActions(mInjector.mSentIntents)).isEmpty();
         // TODO(b/140868593): should have received a USER_UNLOCK_MSG message as well, but it doesn't
-        // because StorageManager.isUserKeyUnlocked(TEST_PRE_CREATED_USER_ID) returns false - to
+        // because StorageManager.isCeStorageUnlocked(TEST_PRE_CREATED_USER_ID) returns false - to
         // properly fix it, we'd need to move this class to FrameworksMockingServicesTests so we can
         // mock static methods (but moving this class would involve changing the presubmit tests,
         // and the cascade effect goes on...). In fact, a better approach would to not assert the
@@ -648,7 +648,7 @@
         // checking.
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(0))
-                .lockUserKey(anyInt());
+                .lockCeStorage(anyInt());
 
         addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
                 numerOfUserSwitches, true);
@@ -663,7 +663,7 @@
         mUserController.finishUserStopped(ussUser1, /* allowDelayedLocking= */ true);
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(1))
-                .lockUserKey(TEST_USER_ID);
+                .lockCeStorage(TEST_USER_ID);
     }
 
     /**
@@ -757,7 +757,7 @@
         mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
 
         verify(mInjector.mStorageManagerMock, never())
-                .unlockUserKey(eq(TEST_USER_ID), anyInt(), any());
+                .unlockCeStorage(eq(TEST_USER_ID), anyInt(), any());
     }
 
     @Test
@@ -1035,7 +1035,7 @@
         mUserController.finishUserStopped(ussUser, delayedLocking);
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
-                .lockUserKey(userId);
+                .lockCeStorage(userId);
     }
 
     private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 31599ee..aba24fb 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -29,13 +29,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.BluetoothProfileConnectionInfo;
 import android.util.Log;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
@@ -54,7 +55,6 @@
     private static final String TAG = "AudioDeviceBrokerTest";
     private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100;
 
-    private Context mContext;
     // the actual class under test
     private AudioDeviceBroker mAudioDeviceBroker;
 
@@ -67,13 +67,13 @@
 
     @Before
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
 
         mMockAudioService = mock(AudioService.class);
         mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
         mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem));
         mSpySystemServer = spy(new NoOpSystemServerAdapter());
-        mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory,
+        mAudioDeviceBroker = new AudioDeviceBroker(context, mMockAudioService, mSpyDevInventory,
                 mSpySystemServer, mSpyAudioSystem);
         mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);
 
@@ -197,6 +197,37 @@
                 any(Intent.class));
     }
 
+    /**
+     * Test that constructing an AdiDeviceState instance requires a non-null address for a
+     * wireless type, but can take null for a non-wireless type;
+     * @throws Exception
+     */
+    @Test
+    public void testAdiDeviceStateNullAddressCtor() throws Exception {
+        try {
+            new AdiDeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                    AudioManager.DEVICE_OUT_SPEAKER, null);
+            new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                    AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, null);
+            Assert.fail();
+        } catch (NullPointerException e) { }
+    }
+
+    @Test
+    public void testAdiDeviceStateStringSerialization() throws Exception {
+        Log.i(TAG, "starting testAdiDeviceStateStringSerialization");
+        final AdiDeviceState devState = new AdiDeviceState(
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioManager.DEVICE_OUT_SPEAKER, "bla");
+        devState.setHasHeadTracker(false);
+        devState.setHeadTrackerEnabled(false);
+        devState.setSAEnabled(true);
+        final String persistString = devState.toPersistableString();
+        final AdiDeviceState result = AdiDeviceState.fromPersistedString(persistString);
+        Log.i(TAG, "original:" + devState);
+        Log.i(TAG, "result  :" + result);
+        Assert.assertEquals(devState, result);
+    }
+
     private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection,
             boolean mockMediaPlayback, boolean guaranteeSingleConnection) throws Exception {
         when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
index 3ad24de..ad09ef0 100644
--- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.audio;
 
-import com.android.server.audio.SpatializerHelper.SADeviceState;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.doNothing;
@@ -26,12 +24,12 @@
 
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
-import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioSystem;
 import android.util.Log;
 
 import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Assert;
@@ -55,72 +53,25 @@
 
     @Mock private AudioService mMockAudioService;
     @Spy private AudioSystemAdapter mSpyAudioSystem;
-    @Mock private AudioSystemAdapter mMockAudioSystem;
+    @Spy private AudioDeviceBroker mSpyDeviceBroker;
 
     @Before
     public void setUp() throws Exception {
         mMockAudioService = mock(AudioService.class);
-    }
 
-    /**
-     * Initializes mSpatHelper, the SpatizerHelper instance under test, to use the mock or spy
-     * AudioSystemAdapter
-     * @param useSpyAudioSystem true to use the spy adapter, mSpyAudioSystem, or false to use
-     *                          the mock adapter, mMockAudioSystem.
-     */
-    private void setUpSpatHelper(boolean useSpyAudioSystem) {
-        final AudioSystemAdapter asAdapter;
-        if (useSpyAudioSystem) {
-            mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
-            asAdapter = mSpyAudioSystem;
-            mMockAudioSystem = null;
-        } else {
-            mSpyAudioSystem = null;
-            mMockAudioSystem = mock(NoOpAudioSystemAdapter.class);
-            asAdapter = mMockAudioSystem;
-        }
-        mSpatHelper = new SpatializerHelper(mMockAudioService, asAdapter,
-                true /*binauralEnabledDefault*/,
-                true /*transauralEnabledDefault*/,
-                false /*headTrackingEnabledDefault*/);
-
-    }
-
-    /**
-     * Test that constructing an SADeviceState instance requires a non-null address for a
-     * wireless type, but can take null for a non-wireless type;
-     * @throws Exception
-     */
-    @Test
-    public void testSADeviceStateNullAddressCtor() throws Exception {
-        setUpSpatHelper(true /*useSpyAudioSystem*/);
-        try {
-            SADeviceState devState = new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
-            devState = new SADeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, null);
-            Assert.fail();
-        } catch (NullPointerException e) { }
+        mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+        mSpyDeviceBroker = spy(
+                new AudioDeviceBroker(
+                        InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                        mMockAudioService, mSpyAudioSystem));
+        mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem,
+                mSpyDeviceBroker, /*binauralEnabledDefault=*/true, /*transauralEnabledDefault=*/
+                true, /*headTrackingEnabledDefault*/false);
     }
 
     @Test
-    public void testSADeviceStateStringSerialization() throws Exception {
-        Log.i(TAG, "starting testSADeviceStateStringSerialization");
-        setUpSpatHelper(true /*useSpyAudioSystem*/);
-        final SADeviceState devState = new SADeviceState(
-                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "bla");
-        devState.mHasHeadTracker = false;
-        devState.mHeadTrackerEnabled = false;
-        devState.mEnabled = true;
-        final String persistString = devState.toPersistableString();
-        final SADeviceState result = SADeviceState.fromPersistedString(persistString);
-        Log.i(TAG, "original:" + devState);
-        Log.i(TAG, "result  :" + result);
-        Assert.assertEquals(devState, result);
-    }
-
-    @Test
-    public void testSADeviceSettings() throws Exception {
+    public void testAdiDeviceStateSettings() throws Exception {
         Log.i(TAG, "starting testSADeviceSettings");
-        setUpSpatHelper(true /*useSpyAudioSystem*/);
         final AudioDeviceAttributes dev1 =
                 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
         final AudioDeviceAttributes dev2 =
@@ -128,7 +79,7 @@
         final AudioDeviceAttributes dev3 =
                 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "R2:D2:bloop");
 
-        doNothing().when(mMockAudioService).persistSpatialAudioDeviceSettings();
+        doNothing().when(mSpyDeviceBroker).persistAudioDeviceSettings();
         mSpatHelper.initForTest(true /*binaural*/, true /*transaural*/);
 
         // test with single device
@@ -163,11 +114,11 @@
      * the original one.
      */
     private void checkAddSettings() throws Exception {
-        String settings = mSpatHelper.getSADeviceSettings();
+        String settings = mSpyDeviceBroker.getDeviceSettings();
         Log.i(TAG, "device settings: " + settings);
-        mSpatHelper.clearSADevices();
-        mSpatHelper.setSADeviceSettings(settings);
-        String settingsRestored = mSpatHelper.getSADeviceSettings();
+        mSpyDeviceBroker.clearDeviceInventory();
+        mSpyDeviceBroker.setDeviceSettings(settings);
+        String settingsRestored = mSpyDeviceBroker.getDeviceSettings();
         Log.i(TAG, "device settingsRestored: " + settingsRestored);
         Assert.assertEquals(settings, settingsRestored);
     }
@@ -179,7 +130,6 @@
     @Test
     public void testNoRoutingCanBeSpatialized() throws Exception {
         Log.i(TAG, "Starting testNoRoutingCanBeSpatialized");
-        setUpSpatHelper(false /*useSpyAudioSystem*/);
         mSpatHelper.forceStateForTest(SpatializerHelper.STATE_ENABLED_AVAILABLE);
 
         final ArrayList<AudioDeviceAttributes> emptyList = new ArrayList<>(0);
@@ -191,12 +141,12 @@
                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                 .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1).build();
 
-        when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
+        when(mSpyAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
                 .thenReturn(emptyList);
         Assert.assertFalse("can be spatialized on empty routing",
                 mSpatHelper.canBeSpatialized(media, spatialFormat));
 
-        when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
+        when(mSpyAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
                 .thenReturn(listWithNull);
         Assert.assertFalse("can be spatialized on null routing",
                 mSpatHelper.canBeSpatialized(media, spatialFormat));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index f88afe7..a78f2dc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -41,6 +41,8 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -53,6 +55,7 @@
 import android.os.Binder;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -84,6 +87,8 @@
     @Rule
     public MockitoRule mockitorule = MockitoJUnit.rule();
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private Context mContext;
     @Mock
@@ -418,6 +423,37 @@
                 eq(callback));
     }
 
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGetLastAuthenticationTime_flaggedOff_throwsUnsupportedOperationException()
+            throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+        setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        mAuthService.mImpl.getLastAuthenticationTime(0,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG);
+    }
+
+    @Test
+    public void testGetLastAuthenticationTime_flaggedOn_callsBiometricService()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+        setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        final int userId = 0;
+        final int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG;
+
+        mAuthService.mImpl.getLastAuthenticationTime(userId, authenticators);
+
+        waitForIdle();
+        verify(mBiometricService).getLastAuthenticationTime(eq(userId), eq(authenticators));
+    }
+
     private static void setInternalAndTestBiometricPermissions(
             Context context, boolean hasPermission) {
         for (String p : List.of(TEST_BIOMETRIC, MANAGE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 662477d..2aabb1b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -104,6 +104,7 @@
     @Mock private KeyStore mKeyStore;
     @Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
     @Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
+    @Mock private BiometricCameraManager mBiometricCameraManager;
 
     private Random mRandom;
     private IBinder mToken;
@@ -571,7 +572,8 @@
                 promptInfo,
                 TEST_PACKAGE,
                 checkDevicePolicyManager,
-                mContext);
+                mContext,
+                mBiometricCameraManager);
     }
 
     private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 67be376..14a567a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -18,7 +18,6 @@
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
 import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
@@ -61,6 +60,7 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -70,12 +70,17 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.keymaster.HardwareAuthenticatorType;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.security.GateKeeper;
 import android.security.KeyStore;
+import android.security.authorization.IKeystoreAuthorization;
+import android.service.gatekeeper.IGateKeeperService;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.WindowManager;
@@ -91,6 +96,7 @@
 import com.android.server.biometrics.sensors.LockoutTracker;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentCaptor;
@@ -104,6 +110,9 @@
 @SmallTest
 public class BiometricServiceTest {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final String TEST_PACKAGE_NAME = "test_package";
     private static final long TEST_REQUEST_ID = 44;
 
@@ -152,11 +161,19 @@
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private BiometricCameraManager mBiometricCameraManager;
+
+    @Mock
+    private IKeystoreAuthorization mKeystoreAuthService;
+
+    @Mock
+    private IGateKeeperService mGateKeeperService;
 
     BiometricContextProvider mBiometricContextProvider;
 
     @Before
-    public void setUp() {
+    public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
 
         resetReceivers();
@@ -178,6 +195,7 @@
         when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
         when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID);
         when(mInjector.getUserManager(any())).thenReturn(mUserManager);
+        when(mInjector.getBiometricCameraManager(any())).thenReturn(mBiometricCameraManager);
 
         when(mResources.getString(R.string.biometric_error_hw_unavailable))
                 .thenReturn(ERROR_HW_UNAVAILABLE);
@@ -193,6 +211,9 @@
                 mStatusBarService, null /* handler */,
                 mAuthSessionCoordinator);
         when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider);
+        when(mInjector.getKeystoreAuthorizationService()).thenReturn(mKeystoreAuthService);
+        when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
+        when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
 
         final String[] config = {
                 "0:2:15",  // ID0:Fingerprint:Strong
@@ -1609,6 +1630,44 @@
         verifyNoMoreInteractions(callback);
     }
 
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGetLastAuthenticationTime_flagOff_throwsUnsupportedOperationException()
+            throws RemoteException {
+        mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.mImpl.getLastAuthenticationTime(0, Authenticators.BIOMETRIC_STRONG);
+    }
+
+    @Test
+    public void testGetLastAuthenticationTime_flagOn_callsKeystoreAuthorization()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
+
+        final int[] hardwareAuthenticators = new int[] {
+                HardwareAuthenticatorType.PASSWORD,
+                HardwareAuthenticatorType.FINGERPRINT
+        };
+
+        final int userId = 0;
+        final long secureUserId = mGateKeeperService.getSecureUserId(userId);
+
+        assertNotEquals(GateKeeper.INVALID_SECURE_USER_ID, secureUserId);
+
+        final long expectedResult = 31337L;
+
+        when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
+                .thenReturn(expectedResult);
+
+        mBiometricService = new BiometricService(mContext, mInjector);
+
+        final long result = mBiometricService.mImpl.getLastAuthenticationTime(userId,
+                Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
+
+        assertEquals(expectedResult, result);
+        verify(mKeystoreAuthService).getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators));
+    }
+
     // Helper methods
 
     private int invokeCanAuthenticate(BiometricService service, int authenticators)
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
new file mode 100644
index 0000000..c2bdf50
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.biometrics;
+
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.ITrustManager;
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.PromptInfo;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class PreAuthInfoTest {
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private static final int SENSOR_ID_FACE = 1;
+    private static final String TEST_PACKAGE_NAME = "PreAuthInfoTestPackage";
+
+    @Mock
+    IBiometricAuthenticator mFaceAuthenticator;
+    @Mock
+    Context mContext;
+    @Mock
+    ITrustManager mTrustManager;
+    @Mock
+    DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    BiometricService.SettingObserver mSettingObserver;
+    @Mock
+    BiometricCameraManager mBiometricCameraManager;
+
+    @Before
+    public void setup() throws RemoteException {
+        when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+        when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
+                .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
+        when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+        when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
+        when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
+                .thenReturn(LOCKOUT_NONE);
+        when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(false);
+        when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(false);
+    }
+
+    @Test
+    public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception {
+        when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(true);
+
+        BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+            @Override
+            boolean confirmationAlwaysRequired(int userId) {
+                return false;
+            }
+
+            @Override
+            boolean confirmationSupported() {
+                return false;
+            }
+        };
+        PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+        PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor),
+                0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+        assertThat(preAuthInfo.eligibleSensors).isEmpty();
+    }
+
+    @Test
+    public void testFaceAuthentication_whenCameraPrivacyIsDisabledAndCameraIsAvailable()
+            throws Exception {
+        BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+            @Override
+            boolean confirmationAlwaysRequired(int userId) {
+                return false;
+            }
+
+            @Override
+            boolean confirmationSupported() {
+                return false;
+            }
+        };
+        PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+        PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor),
+                0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+        assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+    }
+
+    @Test
+    public void testFaceAuthentication_whenCameraIsUnavailable() throws RemoteException {
+        when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(true);
+        BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+                BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+            @Override
+            boolean confirmationAlwaysRequired(int userId) {
+                return false;
+            }
+
+            @Override
+            boolean confirmationSupported() {
+                return false;
+            }
+        };
+        PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+        promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+        PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor),
+                0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+        assertThat(preAuthInfo.eligibleSensors).hasSize(0);
+    }
+}
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 e960e99..f5d50d1 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -254,6 +254,8 @@
                 .thenReturn(true);
         when(res.getBoolean(eq(com.android.internal.R.bool.config_strongAuthRequiredOnBoot)))
                 .thenReturn(true);
+        when(res.getBoolean(eq(com.android.internal.R.bool.config_repairModeSupported)))
+                .thenReturn(true);
         return res;
     }
 
@@ -302,17 +304,17 @@
 
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
-            mStorageManager.unlockUserKey(/* userId= */ (int) args[0],
+            mStorageManager.unlockCeStorage(/* userId= */ (int) args[0],
                     /* secret= */ (byte[]) args[2]);
             return null;
-        }).when(sm).unlockUserKey(anyInt(), anyInt(), any());
+        }).when(sm).unlockCeStorage(anyInt(), anyInt(), any());
 
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
-            mStorageManager.setUserKeyProtection(/* userId= */ (int) args[0],
+            mStorageManager.setCeStorageProtection(/* userId= */ (int) args[0],
                     /* secret= */ (byte[]) args[1]);
             return null;
-        }).when(sm).setUserKeyProtection(anyInt(), any());
+        }).when(sm).setCeStorageProtection(anyInt(), any());
 
         return sm;
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
index 91f3fed..c08ad13 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/FakeStorageManager.java
@@ -24,7 +24,7 @@
 
     private final ArrayMap<Integer, byte[]> mUserSecrets = new ArrayMap<>();
 
-    public void setUserKeyProtection(int userId, byte[] secret) {
+    public void setCeStorageProtection(int userId, byte[] secret) {
         assertThat(mUserSecrets).doesNotContainKey(userId);
         mUserSecrets.put(userId, secret);
     }
@@ -35,7 +35,7 @@
         return secret;
     }
 
-    public void unlockUserKey(int userId, byte[] secret) {
+    public void unlockCeStorage(int userId, byte[] secret) {
         assertThat(mUserSecrets.get(userId)).isEqualTo(secret);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index cfb3b34..fa3c7a4c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -84,6 +84,11 @@
     }
 
     @Override
+    File getRepairModePersistentDataFile() {
+        return remapToStorageDir(super.getRepairModePersistentDataFile());
+    }
+
+    @Override
     PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
         return mPersistentDataBlockManager;
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 39e2792..02b86db 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -457,6 +457,31 @@
         assertEquals(2, PersistentData.TYPE_SP_WEAVER);
     }
 
+    @Test
+    public void testRepairMode_emptyPersistentData() {
+        assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
+    }
+
+    @Test
+    public void testRepairMode_writeGatekeeperPersistentData() {
+        mStorage.writeRepairModePersistentData(
+                PersistentData.TYPE_SP_GATEKEEPER, SOME_USER_ID, PAYLOAD);
+
+        final PersistentData data = mStorage.readRepairModePersistentData();
+        assertEquals(PersistentData.TYPE_SP_GATEKEEPER, data.type);
+        assertArrayEquals(PAYLOAD, data.payload);
+    }
+
+    @Test
+    public void testRepairMode_writeWeaverPersistentData() {
+        mStorage.writeRepairModePersistentData(
+                PersistentData.TYPE_SP_WEAVER, SOME_USER_ID, PAYLOAD);
+
+        final PersistentData data = mStorage.readRepairModePersistentData();
+        assertEquals(PersistentData.TYPE_SP_WEAVER, data.type);
+        assertArrayEquals(PAYLOAD, data.payload);
+    }
+
     private static void assertArrayEquals(byte[] expected, byte[] actual) {
         if (!Arrays.equals(expected, actual)) {
             fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
new file mode 100644
index 0000000..70150c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.locksettings;
+
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
+import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.app.PropertyInvalidatedCache;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LockscreenRepairModeTest extends BaseLockSettingsServiceTests {
+
+    @Before
+    public void setUp() throws Exception {
+        PropertyInvalidatedCache.disableForTestMode();
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void verifyPin_writeRepairModePW() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+    }
+
+    @Test
+    public void verifyPattern_writeRepairModePW() {
+        mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPattern("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+    }
+
+    @Test
+    public void verifyPassword_writeRepairModePW() {
+        mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPassword("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                mService.getCredentialType(USER_REPAIR_MODE));
+    }
+
+    @Test
+    public void verifyCredential_writeRepairModePW_repairModeActive() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        setRepairModeActive(true);
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+    }
+
+    @Test
+    public void deleteRepairModePersistentData() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+
+        mService.deleteRepairModePersistentDataIfNeeded();
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+    }
+
+    @Test
+    public void verifyPin_userRepairMode() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPin("1234"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyPattern_userRepairMode() {
+        mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPattern("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPattern("4321"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyPassword_userRepairMode() {
+        mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPassword("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPassword("4321"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyCredential_userRepairMode_repairModeIsNotActive() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(newPin("1234"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyCredential_userRepairMode_wrongPin() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(newPin("5678"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    private void setRepairModeActive(boolean active) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REPAIR_MODE_ACTIVE, active ? 1 : 0);
+    }
+}
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 37a6d22..eca19c8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -255,7 +255,7 @@
     public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
         initSpAndSetCredential(PRIMARY_USER_ID, newPassword(null));
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
@@ -267,7 +267,7 @@
         mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
 
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
@@ -285,39 +285,39 @@
     @Test
     public void testHeadlessSystemUserDoesNotPassAuthSecret() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
         verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
     public void testHeadlessSecondaryUserPassesAuthSecret() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
     public void testHeadlessTertiaryUserPassesSameAuthSecret() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
         var captor = ArgumentCaptor.forClass(byte[].class);
         verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
         var value = captor.getValue();
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
     }
 
     @Test
     public void testHeadlessTertiaryUserPassesSameAuthSecretAfterReset() throws RemoteException {
         setupHeadlessTest();
-        mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
         var captor = ArgumentCaptor.forClass(byte[].class);
         verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
         var value = captor.getValue();
         mService.clearAuthSecret();
         reset(mAuthSecretService);
-        mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+        mService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
         verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index 5654c89..80fb5e3 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -297,13 +298,15 @@
                 TestData.getInsecureCertPathForEndpoint1());
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, appKeyAlias);
 
+        setExpectedScryptArgument(password.getBytes());
+
         mKeySyncTask.run();
 
         KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                 isEqualTo(UI_FORMAT_PASSWORD);
-        verify(mMockScrypt).scrypt(eq(password.getBytes()), any(),
+        verify(mMockScrypt).scrypt(any(), any(),
                 eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
                 eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
         KeyDerivationParams keyDerivationParams =
@@ -314,6 +317,44 @@
     }
 
     @Test
+    public void run_zeroizedCredential() throws Exception {
+        String password = TrustedRootCertificates.INSECURE_PASSWORD_PREFIX + "123";
+        String appKeyAlias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX + "alias";
+        byte[] zeroizedCredential = password.getBytes();
+        mKeySyncTask = new KeySyncTask(
+                mRecoverableKeyStoreDb,
+                mRecoverySnapshotStorage,
+                mSnapshotListenersStorage,
+                TEST_USER_ID,
+                CREDENTIAL_TYPE_PASSWORD,
+                /*credential=*/ zeroizedCredential,
+                /*credentialUpdated=*/ false,
+                mPlatformKeyManager,
+                mTestOnlyInsecureCertificateHelper,
+                mMockScrypt);
+        mRecoverableKeyStoreDb.setServerParams(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+        mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
+                TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS);
+        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
+                TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS,
+                TestData.getInsecureCertPathForEndpoint1());
+        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, appKeyAlias);
+
+        // Need to check array value during method call since it is modified later.
+        setExpectedScryptArgument(password.getBytes());
+
+        Arrays.fill(zeroizedCredential, (byte) 0);
+        mKeySyncTask.run();
+
+        verify(mMockScrypt).scrypt(any(), any(),
+                eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
+                eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
+    }
+
+    @Test
     public void run_useSha256ToHashPatternInProdMode() throws Exception {
         String pattern = "123456";
         mKeySyncTask = new KeySyncTask(
@@ -368,13 +409,15 @@
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
                 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
 
+        setExpectedScryptArgument(shortPassword.getBytes());
+
         mKeySyncTask.run();
 
         KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                 isEqualTo(UI_FORMAT_PASSWORD);
-        verify(mMockScrypt).scrypt(eq(shortPassword.getBytes()), any(),
+        verify(mMockScrypt).scrypt(any(), any(),
                 eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
                 eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
         KeyDerivationParams keyDerivationParams =
@@ -650,13 +693,15 @@
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
 
+        setExpectedScryptArgument(password.getBytes());
+
         mKeySyncTask.run();
 
         KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                 isEqualTo(UI_FORMAT_PASSWORD);
-        verify(mMockScrypt).scrypt(eq(password.getBytes()), any(),
+        verify(mMockScrypt).scrypt(any(), any(),
                 eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
                 eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
     }
@@ -681,6 +726,8 @@
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
 
+        setExpectedScryptArgument(pin.getBytes());
+
         mKeySyncTask.run();
 
         KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
@@ -688,7 +735,7 @@
         // Password with only digits is changed to pin.
         assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                 isEqualTo(UI_FORMAT_PIN);
-        verify(mMockScrypt).scrypt(eq(pin.getBytes()), any(),
+        verify(mMockScrypt).scrypt(any(), any(),
                 eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
                 eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
     }
@@ -868,4 +915,14 @@
         new Random().nextBytes(bytes);
         return bytes;
     }
+
+    private void setExpectedScryptArgument(byte[] credentials) {
+        doAnswer(invocation -> {
+            assertThat((byte[]) invocation.getArguments()[0]).isEqualTo(credentials);
+            return invocation.callRealMethod();
+        }).when(mMockScrypt).scrypt(any(), any(),
+                eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
+                eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
+
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index c546a74..c09e09c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
 
 import android.app.KeyguardManager;
 import android.content.Context;
@@ -54,6 +55,7 @@
 
 import java.io.File;
 import java.security.KeyStore;
+import java.security.KeyStoreException;
 import java.security.UnrecoverableKeyException;
 import java.util.List;
 
@@ -393,6 +395,18 @@
     }
 
     @Test
+    public void getEncryptKey_noScreenlock() throws Exception {
+        when(mKeyguardManager.isDeviceSecure(USER_ID_FIXTURE)).thenReturn(false);
+        doThrow(new KeyStoreException()).when(mKeyStoreProxy).setEntry(
+                anyString(),
+                any(),
+                any());
+
+        assertThrows(InsecureUserException.class,
+                () -> mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE));
+    }
+
+    @Test
     public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
                 eq(DECRYPTION_KEY_ALIAS_1),
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 af144cf..13dc120 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -57,6 +57,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.app.IBatteryStats;
+import com.android.modules.utils.build.SdkLevel;
 
 import org.junit.After;
 import org.junit.Before;
@@ -263,7 +264,11 @@
         verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID);
 
         mNMService.setDataSaverModeEnabled(true);
-        verify(mNetdService).bandwidthEnableDataSaver(true);
+        if (SdkLevel.isAtLeastV()) {
+            verify(mCm).setDataSaverEnabled(true);
+        } else {
+            verify(mNetdService).bandwidthEnableDataSaver(true);
+        }
 
         mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
         assertTrue("Should be true since data saver is on and the uid is not allowlisted",
@@ -279,7 +284,11 @@
         mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
         verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID);
         mNMService.setDataSaverModeEnabled(false);
-        verify(mNetdService).bandwidthEnableDataSaver(false);
+        if (SdkLevel.isAtLeastV()) {
+            verify(mCm).setDataSaverEnabled(false);
+        } else {
+            verify(mNetdService).bandwidthEnableDataSaver(false);
+        }
         assertFalse("Network should not be restricted when data saver is off",
                 mNMService.isNetworkRestricted(TEST_UID));
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 0ccb0d0..9e0d69b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1040,14 +1040,25 @@
 
         // If the task has a non-stopped activity, the removal will wait for its onDestroy.
         final Task task = tasks.get(0);
+        final ActivityRecord bottom = new ActivityBuilder(mAtm).setTask(task).build();
         final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).build();
-        top.lastVisibleTime = 123;
+        bottom.lastVisibleTime = top.lastVisibleTime = 123;
         top.setState(ActivityRecord.State.RESUMED, "test");
         mRecentTasks.removeTasksByPackageName(task.getBasePackageName(), TEST_USER_0_ID);
         assertTrue(task.mKillProcessesOnDestroyed);
         top.setState(ActivityRecord.State.DESTROYING, "test");
+        bottom.destroyed("test");
+        assertTrue("Wait for all destroyed", task.mKillProcessesOnDestroyed);
         top.destroyed("test");
-        assertFalse(task.mKillProcessesOnDestroyed);
+        assertFalse("Consume kill", task.mKillProcessesOnDestroyed);
+
+        // If the process is died, the state should be cleared.
+        final Task lastTask = tasks.get(0);
+        lastTask.intent.setComponent(top.mActivityComponent);
+        lastTask.addChild(top);
+        lastTask.mKillProcessesOnDestroyed = true;
+        top.handleAppDied();
+        assertFalse(lastTask.mKillProcessesOnDestroyed);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 3bc6450..b92cc64 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -870,7 +870,7 @@
                 new TestDisplayContent.Builder(mAtm, 1000, 1500)
                         .setSystemDecorations(true).build();
 
-        // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false.
+        // Use invalid user id to let StorageManager.isCeStorageUnlocked() return false.
         final int currentUser = mRootWindowContainer.mCurrentUser;
         mRootWindowContainer.mCurrentUser = -1;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 3ec6f42..973ab84 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -448,7 +448,6 @@
         mDisplayContent.updateImeParent();
 
         // Ime should on top of the popup IME layering target window.
-        mDisplayContent.assignChildLayers(mTransaction);
         assertWindowHigher(mImeWindow, popupImeTargetWin);
     }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 216f45a..d722f2f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -279,6 +279,7 @@
                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
                         Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY
                                 | Context.BIND_SCHEDULE_LIKE_TOP_APP
+                                | Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
                                 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                         new UserHandle(mUser));
             }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index a72f780..89ef523 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2352,6 +2352,11 @@
      * <p>
      * <b>Note</b>: {@link android.app.Notification.CallStyle} notifications should be posted after
      * the call is placed in order for the notification to be non-dismissible.
+     * <p><b>Note</b>: Call Forwarding MMI codes can only be dialed by applications that are
+     * configured as the user defined default dialer or system dialer role. If a call containing a
+     * call forwarding MMI code is placed by an application that is not in one of these roles, the
+     * dialer will be launched with a UI showing the MMI code already populated so that the user can
+     * confirm the action before the call is placed.
      * @param address The address to make the call to.
      * @param extras Bundle of extras to use with the call.
      */
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 3158ad8..287aa65 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -7,10 +7,12 @@
 tgunn@google.com
 huiwang@google.com
 jayachandranc@google.com
-chinmayd@google.com
 amruthr@google.com
 sasindran@google.com
 
 # Requiring TL ownership for new carrier config keys.
 per-file CarrierConfigManager.java=set noparent
 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
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index 29152f1..7b6a201 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -202,6 +202,24 @@
                     && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds;
         }
 
+        private static String barringTypeToString(@BarringType int barringType) {
+            return switch (barringType) {
+                case BARRING_TYPE_NONE -> "NONE";
+                case BARRING_TYPE_CONDITIONAL -> "CONDITIONAL";
+                case BARRING_TYPE_UNCONDITIONAL -> "UNCONDITIONAL";
+                case BARRING_TYPE_UNKNOWN -> "UNKNOWN";
+                default -> "UNKNOWN(" + barringType + ")";
+            };
+        }
+
+        @Override
+        public String toString() {
+            return "BarringServiceInfo {mBarringType=" + barringTypeToString(mBarringType)
+                    + ", mIsConditionallyBarred=" + mIsConditionallyBarred
+                    + ", mConditionalBarringFactor=" + mConditionalBarringFactor
+                    + ", mConditionalBarringTimeSeconds=" + mConditionalBarringTimeSeconds + "}";
+        }
+
         /** @hide */
         public BarringServiceInfo(Parcel p) {
             mBarringType = p.readInt();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 749ed03..43dab53 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8770,7 +8770,9 @@
          * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_NONE},
          * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_1024_BIT_MODP},
          * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_1536_BIT_MODP},
-         * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_2048_BIT_MODP}
+         * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_2048_BIT_MODP},
+         * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_3072_BIT_MODP},
+         * {@link android.net.ipsec.ike.SaProposal#DH_GROUP_4096_BIT_MODP}
          */
         public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY =
                 KEY_PREFIX + "diffie_hellman_groups_int_array";
@@ -8838,7 +8840,8 @@
 
         /**
          * List of supported encryption algorithms for child session. Possible values are
-         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC}
+         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC},
+         * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR}
          */
         public static final String KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY =
                 KEY_PREFIX + "supported_child_session_encryption_algorithms_int_array";
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index 241e691..daaab33 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.test.mock {
 
   @Deprecated public class MockAccountManager {
@@ -158,6 +156,7 @@
     method @Deprecated public int getInt(int);
     method @Deprecated public long getLong(int);
     method @Deprecated public android.net.Uri getNotificationUri();
+    method @Deprecated public java.util.List<android.net.Uri> getNotificationUris();
     method @Deprecated public int getPosition();
     method @Deprecated public short getShort(int);
     method @Deprecated public String getString(int);
@@ -181,6 +180,7 @@
     method @Deprecated public android.os.Bundle respond(android.os.Bundle);
     method @Deprecated public void setExtras(android.os.Bundle);
     method @Deprecated public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
+    method @Deprecated public void setNotificationUris(android.content.ContentResolver, java.util.List<android.net.Uri>);
     method @Deprecated public void unregisterContentObserver(android.database.ContentObserver);
     method @Deprecated public void unregisterDataSetObserver(android.database.DataSetObserver);
   }
@@ -202,7 +202,7 @@
     method @Deprecated public int checkPermission(String, String);
     method @Deprecated public int checkSignatures(String, String);
     method @Deprecated public int checkSignatures(int, int);
-    method public void clearInstantAppCookie();
+    method @Deprecated public void clearInstantAppCookie();
     method @Deprecated public void clearPackagePreferredActivities(String);
     method @Deprecated public String[] currentToCanonicalPackageNames(String[]);
     method @Deprecated public void extendVerificationTimeout(int, int, long);
@@ -224,15 +224,15 @@
     method @Deprecated public CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
     method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
     method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.ChangedPackages getChangedPackages(int);
+    method @Deprecated public android.content.pm.ChangedPackages getChangedPackages(int);
     method @Deprecated public int getComponentEnabledSetting(android.content.ComponentName);
     method @Deprecated public android.graphics.drawable.Drawable getDefaultActivityIcon();
     method @Deprecated public android.graphics.drawable.Drawable getDrawable(String, int, android.content.pm.ApplicationInfo);
     method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
     method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
     method @Deprecated public String getInstallerPackageName(String);
-    method public byte[] getInstantAppCookie();
-    method public int getInstantAppCookieMaxBytes();
+    method @Deprecated public byte[] getInstantAppCookie();
+    method @Deprecated public int getInstantAppCookieMaxBytes();
     method @Deprecated public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.Intent getLaunchIntentForPackage(String);
     method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String);
@@ -241,7 +241,7 @@
     method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.PackageInstaller getPackageInstaller();
+    method @Deprecated public android.content.pm.PackageInstaller getPackageInstaller();
     method @Deprecated public int getPackageUid(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public String[] getPackagesForUid(int);
     method @Deprecated public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(String[], int);
@@ -265,8 +265,8 @@
     method @Deprecated public android.content.res.XmlResourceParser getXml(String, int, android.content.pm.ApplicationInfo);
     method @Deprecated public boolean hasSystemFeature(String);
     method @Deprecated public boolean hasSystemFeature(String, int);
-    method public boolean isInstantApp();
-    method public boolean isInstantApp(String);
+    method @Deprecated public boolean isInstantApp();
+    method @Deprecated public boolean isInstantApp(String);
     method @Deprecated public boolean isPermissionRevokedByPolicy(String, String);
     method @Deprecated public boolean isSafeMode();
     method @Deprecated public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -283,11 +283,12 @@
     method @Deprecated public android.content.pm.ProviderInfo resolveContentProvider(String, int);
     method @Deprecated public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
     method @Deprecated public android.content.pm.ResolveInfo resolveServiceAsUser(android.content.Intent, int, int);
-    method public void setApplicationCategoryHint(String, int);
+    method @Deprecated public void setApplicationCategoryHint(String, int);
     method @Deprecated public void setApplicationEnabledSetting(String, int, int);
     method @Deprecated public void setComponentEnabledSetting(android.content.ComponentName, int, int);
     method @Deprecated public void setInstallerPackageName(String, String);
-    method public void updateInstantAppCookie(@NonNull byte[]);
+    method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
+    method @Deprecated public void updateInstantAppCookie(@NonNull byte[]);
     method @Deprecated public void verifyPendingInstall(int, int);
   }
 
diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt
index fa2fbd2..0c800b6 100644
--- a/test-mock/api/removed.txt
+++ b/test-mock/api/removed.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.test.mock {
 
   public class MockContext extends android.content.Context {
@@ -11,7 +9,7 @@
   @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
     method @Deprecated public String getDefaultBrowserPackageName(int);
     method @Deprecated public boolean setDefaultBrowserPackageName(String, int);
-    method public boolean setInstantAppCookie(@NonNull byte[]);
+    method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);
   }
 
 }
diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt
index 2b5132e..f350957 100644
--- a/test-mock/api/system-current.txt
+++ b/test-mock/api/system-current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.test.mock {
 
   public class MockContext extends android.content.Context {
@@ -11,30 +9,30 @@
   }
 
   @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
-    method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
-    method public boolean arePermissionsIndividuallyControlled();
+    method @Deprecated public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+    method @Deprecated public boolean arePermissionsIndividuallyControlled();
     method @Deprecated public java.util.List<android.content.IntentFilter> getAllIntentFilters(String);
-    method public String getDefaultBrowserPackageNameAsUser(int);
-    method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
-    method public android.graphics.drawable.Drawable getInstantAppIcon(String);
-    method public android.content.ComponentName getInstantAppInstallerComponent();
-    method public android.content.ComponentName getInstantAppResolverSettingsComponent();
-    method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
-    method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
-    method public int getIntentVerificationStatusAsUser(String, int);
-    method public int getPermissionFlags(String, String, android.os.UserHandle);
-    method public void grantRuntimePermission(String, String, android.os.UserHandle);
-    method public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
-    method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
-    method public void revokeRuntimePermission(String, String, android.os.UserHandle);
-    method public boolean setDefaultBrowserPackageNameAsUser(String, int);
+    method @Deprecated public String getDefaultBrowserPackageNameAsUser(int);
+    method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+    method @Deprecated public android.graphics.drawable.Drawable getInstantAppIcon(String);
+    method @Deprecated public android.content.ComponentName getInstantAppInstallerComponent();
+    method @Deprecated public android.content.ComponentName getInstantAppResolverSettingsComponent();
+    method @Deprecated public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
+    method @Deprecated public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String);
+    method @Deprecated public int getIntentVerificationStatusAsUser(String, int);
+    method @Deprecated public int getPermissionFlags(String, String, android.os.UserHandle);
+    method @Deprecated public void grantRuntimePermission(String, String, android.os.UserHandle);
+    method @Deprecated public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
+    method @Deprecated public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+    method @Deprecated public void revokeRuntimePermission(String, String, android.os.UserHandle);
+    method @Deprecated public boolean setDefaultBrowserPackageNameAsUser(String, int);
     method public String[] setPackagesSuspended(String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, String);
-    method public void setUpdateAvailable(String, boolean);
-    method public boolean updateIntentVerificationStatusAsUser(String, int, int);
-    method public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
-    method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
+    method @Deprecated public void setUpdateAvailable(String, boolean);
+    method @Deprecated public boolean updateIntentVerificationStatusAsUser(String, int, int);
+    method @Deprecated public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
+    method @Deprecated public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
   }
 
 }
diff --git a/test-mock/api/system-removed.txt b/test-mock/api/system-removed.txt
index 14191eb..d802177 100644
--- a/test-mock/api/system-removed.txt
+++ b/test-mock/api/system-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt
index 1752edc..9ed0108 100644
--- a/test-mock/api/test-current.txt
+++ b/test-mock/api/test-current.txt
@@ -1,6 +1,4 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
 package android.test.mock {
 
   public class MockContext extends android.content.Context {
@@ -8,13 +6,13 @@
   }
 
   @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {
-    method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
-    method public void clearCrossProfileIntentFilters(int);
-    method public int getInstallReason(String, android.os.UserHandle);
-    method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
-    method public String[] getNamesForUids(int[]);
-    method @NonNull public String getServicesSystemSharedLibraryPackageName();
-    method @NonNull public String getSharedSystemSharedLibraryPackageName();
+    method @Deprecated public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
+    method @Deprecated public void clearCrossProfileIntentFilters(int);
+    method @Deprecated public int getInstallReason(String, android.os.UserHandle);
+    method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+    method @Deprecated public String[] getNamesForUids(int[]);
+    method @Deprecated @NonNull public String getServicesSystemSharedLibraryPackageName();
+    method @Deprecated @NonNull public String getSharedSystemSharedLibraryPackageName();
   }
 
 }
diff --git a/test-mock/api/test-removed.txt b/test-mock/api/test-removed.txt
index 14191eb..d802177 100644
--- a/test-mock/api/test-removed.txt
+++ b/test-mock/api/test-removed.txt
@@ -1,3 +1 @@
 // Signature format: 2.0
-// - add-additional-overrides=no
-// - migrating=Migration in progress see b/299366704
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index a1a39ff..59dc689 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -117,6 +117,16 @@
         return buildTestConfig(UNDERLYING_NETWORK_TEMPLATES);
     }
 
+    // Public for use in VcnGatewayConnectionTest
+    public static VcnGatewayConnectionConfig.Builder newTestBuilderMinimal() {
+        final VcnGatewayConnectionConfig.Builder builder = newBuilder();
+        for (int caps : EXPOSED_CAPS) {
+            builder.addExposedCapability(caps);
+        }
+
+        return builder;
+    }
+
     private static VcnGatewayConnectionConfig.Builder newBuilder() {
         // Append a unique identifier to the name prefix to guarantee that all created
         // VcnGatewayConnectionConfigs have a unique name (required by VcnConfig).
@@ -125,6 +135,17 @@
                 TUNNEL_CONNECTION_PARAMS);
     }
 
+    private static VcnGatewayConnectionConfig.Builder newBuilderMinimal() {
+        final VcnGatewayConnectionConfig.Builder builder =
+                new VcnGatewayConnectionConfig.Builder(
+                        "newBuilderMinimal", TUNNEL_CONNECTION_PARAMS);
+        for (int caps : EXPOSED_CAPS) {
+            builder.addExposedCapability(caps);
+        }
+
+        return builder;
+    }
+
     private static VcnGatewayConnectionConfig buildTestConfigWithExposedCapsAndOptions(
             VcnGatewayConnectionConfig.Builder builder,
             Set<Integer> gatewayOptions,
@@ -273,6 +294,7 @@
 
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis());
         assertEquals(MAX_MTU, config.getMaxMtu());
+        assertTrue(config.isSafeModeEnabled());
 
         assertFalse(
                 config.hasGatewayOption(
@@ -290,6 +312,14 @@
     }
 
     @Test
+    public void testBuilderAndGettersSafeModeDisabled() {
+        final VcnGatewayConnectionConfig config =
+                newBuilderMinimal().setSafeModeEnabled(false).build();
+
+        assertFalse(config.isSafeModeEnabled());
+    }
+
+    @Test
     public void testPersistableBundle() {
         final VcnGatewayConnectionConfig config = buildTestConfig();
 
@@ -305,6 +335,14 @@
     }
 
     @Test
+    public void testPersistableBundleSafeModeDisabled() {
+        final VcnGatewayConnectionConfig config =
+                newBuilderMinimal().setSafeModeEnabled(false).build();
+
+        assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle()));
+    }
+
+    @Test
     public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() {
         PersistableBundle configBundle = buildTestConfig().toPersistableBundle();
         configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null);
@@ -411,4 +449,18 @@
         assertEquals(config, configEqual);
         assertNotEquals(config, configNotEqual);
     }
+
+    @Test
+    public void testSafeModeEnableDisableEquality() throws Exception {
+        final VcnGatewayConnectionConfig config = newBuilderMinimal().build();
+        final VcnGatewayConnectionConfig configEqual = newBuilderMinimal().build();
+
+        assertEquals(config.isSafeModeEnabled(), configEqual.isSafeModeEnabled());
+
+        final VcnGatewayConnectionConfig configNotEqual =
+                newBuilderMinimal().setSafeModeEnabled(false).build();
+
+        assertEquals(config, configEqual);
+        assertNotEquals(config, configNotEqual);
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 302af52..f846164 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -75,6 +75,9 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback;
+import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
+import com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
+import com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent;
 import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
 import com.android.server.vcn.util.MtuUtils;
 
@@ -651,6 +654,74 @@
         verifySafeModeStateAndCallbackFired(2 /* invocationCount */, true /* isInSafeMode */);
     }
 
+    private void verifySetSafeModeAlarm(
+            boolean safeModeEnabledByCaller,
+            boolean safeModeConfigFlagEnabled,
+            boolean expectingSafeModeEnabled)
+            throws Exception {
+        final VcnGatewayConnectionConfig config =
+                VcnGatewayConnectionConfigTest.newTestBuilderMinimal()
+                        .setSafeModeEnabled(safeModeEnabledByCaller)
+                        .build();
+        final VcnGatewayConnection.Dependencies deps =
+                mock(VcnGatewayConnection.Dependencies.class);
+        setUpWakeupMessage(
+                mSafeModeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM, deps);
+        doReturn(safeModeConfigFlagEnabled).when(mFeatureFlags).safeModeConfig();
+
+        final VcnGatewayConnection connection =
+                new VcnGatewayConnection(
+                        mVcnContext,
+                        TEST_SUB_GRP,
+                        TEST_SUBSCRIPTION_SNAPSHOT,
+                        config,
+                        mGatewayStatusCallback,
+                        true /* isMobileDataEnabled */,
+                        deps);
+
+        connection.setSafeModeAlarm();
+
+        final int expectedCallCnt = expectingSafeModeEnabled ? 1 : 0;
+        verify(deps, times(expectedCallCnt))
+                .newWakeupMessage(
+                        eq(mVcnContext),
+                        any(),
+                        eq(VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM),
+                        any());
+    }
+
+    @Test
+    public void testSafeModeEnabled_configFlagEnabled() throws Exception {
+        verifySetSafeModeAlarm(
+                true /* safeModeEnabledByCaller */,
+                true /* safeModeConfigFlagEnabled */,
+                true /* expectingSafeModeEnabled */);
+    }
+
+    @Test
+    public void testSafeModeEnabled_configFlagDisabled() throws Exception {
+        verifySetSafeModeAlarm(
+                true /* safeModeEnabledByCaller */,
+                false /* safeModeConfigFlagEnabled */,
+                true /* expectingSafeModeEnabled */);
+    }
+
+    @Test
+    public void testSafeModeDisabled_configFlagEnabled() throws Exception {
+        verifySetSafeModeAlarm(
+                false /* safeModeEnabledByCaller */,
+                true /* safeModeConfigFlagEnabled */,
+                false /* expectingSafeModeEnabled */);
+    }
+
+    @Test
+    public void testSafeModeDisabled_configFlagDisabled() throws Exception {
+        verifySetSafeModeAlarm(
+                false /* safeModeEnabledByCaller */,
+                false /* safeModeConfigFlagEnabled */,
+                true /* expectingSafeModeEnabled */);
+    }
+
     private Consumer<VcnNetworkAgent> setupNetworkAndGetUnwantedCallback() {
         triggerChildOpened();
         mTestLooper.dispatchAll();
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 5efbf59..edced87 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -53,6 +53,7 @@
 import android.net.ipsec.ike.IkeSessionCallback;
 import android.net.ipsec.ike.IkeSessionConfiguration;
 import android.net.ipsec.ike.IkeSessionConnectionInfo;
+import android.net.vcn.FeatureFlags;
 import android.net.vcn.VcnGatewayConnectionConfig;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.os.ParcelUuid;
@@ -165,6 +166,7 @@
     @NonNull protected final Context mContext;
     @NonNull protected final TestLooper mTestLooper;
     @NonNull protected final VcnNetworkProvider mVcnNetworkProvider;
+    @NonNull protected final FeatureFlags mFeatureFlags;
     @NonNull protected final VcnContext mVcnContext;
     @NonNull protected final VcnGatewayConnectionConfig mConfig;
     @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback;
@@ -190,6 +192,7 @@
         mContext = mock(Context.class);
         mTestLooper = new TestLooper();
         mVcnNetworkProvider = mock(VcnNetworkProvider.class);
+        mFeatureFlags = mock(FeatureFlags.class);
         mVcnContext = mock(VcnContext.class);
         mConfig = VcnGatewayConnectionConfigTest.buildTestConfig();
         mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class);
@@ -222,6 +225,7 @@
         doReturn(mContext).when(mVcnContext).getContext();
         doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
         doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
+        doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
@@ -241,8 +245,15 @@
         doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime();
     }
 
+    protected void setUpWakeupMessage(
+            @NonNull WakeupMessage msg,
+            @NonNull String cmdName,
+            VcnGatewayConnection.Dependencies deps) {
+        doReturn(msg).when(deps).newWakeupMessage(eq(mVcnContext), any(), eq(cmdName), any());
+    }
+
     private void setUpWakeupMessage(@NonNull WakeupMessage msg, @NonNull String cmdName) {
-        doReturn(msg).when(mDeps).newWakeupMessage(eq(mVcnContext), any(), eq(cmdName), any());
+        setUpWakeupMessage(msg, cmdName, mDeps);
     }
 
     @Before
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 0d6dc35..40cba3e 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -119,9 +119,9 @@
         "io/Util.cpp",
         "io/ZipArchive.cpp",
         "link/AutoVersioner.cpp",
+        "link/FeatureFlagsFilter.cpp",
         "link/ManifestFixer.cpp",
         "link/NoDefaultResourceRemover.cpp",
-        "link/ProductFilter.cpp",
         "link/PrivateAttributeMover.cpp",
         "link/ReferenceLinker.cpp",
         "link/ResourceExcluder.cpp",
@@ -134,6 +134,7 @@
         "optimize/ResourceFilter.cpp",
         "optimize/Obfuscator.cpp",
         "optimize/VersionCollapser.cpp",
+        "process/ProductFilter.cpp",
         "process/SymbolTable.cpp",
         "split/TableSplitter.cpp",
         "text/Printer.cpp",
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 03f9715..bb7b13a 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -45,6 +45,7 @@
 #include "io/StringStream.h"
 #include "io/Util.h"
 #include "io/ZipArchive.h"
+#include "process/ProductFilter.h"
 #include "trace/TraceBuffer.h"
 #include "util/Files.h"
 #include "util/Util.h"
@@ -179,6 +180,15 @@
     if (!res_parser.Parse(&xml_parser)) {
       return false;
     }
+
+    if (options.product_.has_value()) {
+      if (!ProductFilter({*options.product_}, /* remove_default_config_values = */ true)
+               .Consume(context, &table)) {
+        context->GetDiagnostics()->Error(android::DiagMessage(path_data.source)
+                                         << "failed to filter product");
+        return false;
+      }
+    }
   }
 
   if (options.pseudolocalize && translatable_file) {
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index 14a730a..984d890 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -42,6 +42,7 @@
   // See comments on aapt::ResourceParserOptions.
   bool preserve_visibility_of_styleables = false;
   bool verbose = false;
+  std::optional<std::string> product_;
 };
 
 /** Parses flags and compiles resources to be used in linking.  */
@@ -76,6 +77,10 @@
     AddOptionalFlag("--source-path",
                       "Sets the compiled resource file source file path to the given string.",
                       &options_.source_path);
+    AddOptionalFlag("--filter-product",
+                    "Leave only resources specific to the given product. All "
+                    "other resources (including defaults) are removed.",
+                    &options_.product_);
   }
 
   int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 97404fc..1fb5cc0 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -66,6 +66,7 @@
 #include "optimize/ResourceDeduper.h"
 #include "optimize/VersionCollapser.h"
 #include "process/IResourceTableConsumer.h"
+#include "process/ProductFilter.h"
 #include "process/SymbolTable.h"
 #include "split/TableSplitter.h"
 #include "trace/TraceBuffer.h"
@@ -2127,7 +2128,7 @@
                                          << "can't select products when building static library");
       }
     } else {
-      ProductFilter product_filter(options_.products);
+      ProductFilter product_filter(options_.products, /* remove_default_config_values = */ false);
       if (!product_filter.Consume(context_, &final_table_)) {
         context_->GetDiagnostics()->Error(android::DiagMessage() << "failed stripping products");
         return 1;
@@ -2512,6 +2513,28 @@
     }
   }
 
+  // Parse the feature flag values. An argument that starts with '@' points to a file to read flag
+  // values from.
+  std::vector<std::string> all_feature_flags_args;
+  for (const std::string& arg : feature_flags_args_) {
+    if (util::StartsWith(arg, "@")) {
+      const std::string path = arg.substr(1, arg.size() - 1);
+      std::string error;
+      if (!file::AppendArgsFromFile(path, &all_feature_flags_args, &error)) {
+        context.GetDiagnostics()->Error(android::DiagMessage(path) << error);
+        return 1;
+      }
+    } else {
+      all_feature_flags_args.push_back(arg);
+    }
+  }
+
+  for (const std::string& arg : all_feature_flags_args) {
+    if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
+      return 1;
+    }
+  }
+
   if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path_) {
     if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path_.value(),
         &options_.stable_id_map)) {
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 1b1e93bd..26713fd 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -17,11 +17,17 @@
 #ifndef AAPT2_LINK_H
 #define AAPT2_LINK_H
 
+#include <optional>
 #include <regex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
 
 #include "Command.h"
 #include "Resource.h"
 #include "androidfw/IDiagnostics.h"
+#include "cmd/Util.h"
 #include "format/binary/TableFlattener.h"
 #include "format/proto/ProtoSerialize.h"
 #include "link/ManifestFixer.h"
@@ -72,6 +78,7 @@
   bool use_sparse_encoding = false;
   std::unordered_set<std::string> extensions_to_not_compress;
   std::optional<std::regex> regex_to_not_compress;
+  FeatureFlagValues feature_flag_values;
 
   // Static lib options.
   bool no_static_lib_packages = false;
@@ -323,6 +330,7 @@
             "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_);
   }
 
   int Action(const std::vector<std::string>& args) override;
@@ -347,6 +355,7 @@
   std::optional<std::string> stable_id_file_path_;
   std::vector<std::string> split_args_;
   std::optional<std::string> trace_folder_;
+  std::vector<std::string> feature_flags_args_;
 };
 
 }// namespace aapt
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index a92f24b..678d846 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -113,6 +113,56 @@
   return std::move(filter);
 }
 
+bool ParseFeatureFlagsParameter(StringPiece arg, android::IDiagnostics* diag,
+                                FeatureFlagValues* out_feature_flag_values) {
+  if (arg.empty()) {
+    return true;
+  }
+
+  for (StringPiece flag_and_value : util::Tokenize(arg, ',')) {
+    std::vector<std::string> parts = util::Split(flag_and_value, '=');
+    if (parts.empty()) {
+      continue;
+    }
+
+    if (parts.size() > 2) {
+      diag->Error(android::DiagMessage()
+                  << "Invalid feature flag and optional value '" << flag_and_value
+                  << "'. Must be in the format 'flag_name[=true|false]");
+      return false;
+    }
+
+    StringPiece flag_name = util::TrimWhitespace(parts[0]);
+    if (flag_name.empty()) {
+      diag->Error(android::DiagMessage() << "No name given for one or more flags in: " << arg);
+      return false;
+    }
+
+    std::optional<bool> flag_value = {};
+    if (parts.size() == 2) {
+      StringPiece str_flag_value = util::TrimWhitespace(parts[1]);
+      if (!str_flag_value.empty()) {
+        flag_value = ResourceUtils::ParseBool(parts[1]);
+        if (!flag_value.has_value()) {
+          diag->Error(android::DiagMessage() << "Invalid value for feature flag '" << flag_and_value
+                                             << "'. Value must be 'true' or 'false'");
+          return false;
+        }
+      }
+    }
+
+    if (auto [it, inserted] =
+            out_feature_flag_values->try_emplace(std::string(flag_name), flag_value);
+        !inserted) {
+      // We are allowing the same flag to appear multiple times, last value wins.
+      diag->Note(android::DiagMessage()
+                 << "Value for feature flag '" << flag_name << "' was given more than once");
+      it->second = flag_value;
+    }
+  }
+  return true;
+}
+
 // Adjust the SplitConstraints so that their SDK version is stripped if it
 // is less than or equal to the minSdk. Otherwise the resources that have had
 // their SDK version stripped due to minSdk won't ever match.
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 712c07b..9ece5dd 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -17,8 +17,13 @@
 #ifndef AAPT_SPLIT_UTIL_H
 #define AAPT_SPLIT_UTIL_H
 
+#include <functional>
+#include <map>
+#include <memory>
+#include <optional>
 #include <regex>
 #include <set>
+#include <string>
 #include <unordered_set>
 
 #include "AppInfo.h"
@@ -32,6 +37,8 @@
 
 namespace aapt {
 
+using FeatureFlagValues = std::map<std::string, std::optional<bool>, std::less<>>;
+
 // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc).
 // Returns Nothing and logs a human friendly error message if the string was not legal.
 std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg,
@@ -48,6 +55,13 @@
 std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args,
                                                            android::IDiagnostics* diag);
 
+// Parses a feature flags parameter, which can contain one or more pairs of flag names and optional
+// values, and fills in `out_feature_flag_values` with the parsed values. The pairs in the argument
+// are separated by ',' and the name is separated from the value by '=' if there is a value given.
+// Example arg: "flag1=true,flag2=false,flag3=,flag4" where flag3 and flag4 have no given value.
+bool ParseFeatureFlagsParameter(android::StringPiece arg, android::IDiagnostics* diag,
+                                FeatureFlagValues* out_feature_flag_values);
+
 // Adjust the SplitConstraints so that their SDK version is stripped if it
 // is less than or equal to the min_sdk. Otherwise the resources that have had
 // their SDK version stripped due to min_sdk won't ever match.
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 139bfbc..723d87e 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -25,6 +25,7 @@
 #include "util/Files.h"
 
 using ::android::ConfigDescription;
+using testing::Pair;
 using testing::UnorderedElementsAre;
 
 namespace aapt {
@@ -354,6 +355,51 @@
   EXPECT_CONFIG_EQ(constraints, expected_configuration);
 }
 
+TEST(UtilTest, ParseFeatureFlagsParameter_Empty) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_TRUE(ParseFeatureFlagsParameter("", diagnostics, &feature_flag_values));
+  EXPECT_TRUE(feature_flag_values.empty());
+}
+
+TEST(UtilTest, ParseFeatureFlagsParameter_TooManyParts) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_FALSE(ParseFeatureFlagsParameter("foo=bar=baz", diagnostics, &feature_flag_values));
+}
+
+TEST(UtilTest, ParseFeatureFlagsParameter_NoNameGiven) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,=false", diagnostics, &feature_flag_values));
+}
+
+TEST(UtilTest, ParseFeatureFlagsParameter_InvalidValue) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,bar=42", diagnostics, &feature_flag_values));
+}
+
+TEST(UtilTest, ParseFeatureFlagsParameter_DuplicateFlag) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_TRUE(
+      ParseFeatureFlagsParameter("foo=true,bar=true,foo=false", diagnostics, &feature_flag_values));
+  EXPECT_THAT(feature_flag_values, UnorderedElementsAre(Pair("foo", std::optional<bool>(false)),
+                                                        Pair("bar", std::optional<bool>(true))));
+}
+
+TEST(UtilTest, ParseFeatureFlagsParameter_Valid) {
+  auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
+  FeatureFlagValues feature_flag_values;
+  ASSERT_TRUE(ParseFeatureFlagsParameter("foo= true, bar =FALSE,baz=, quux", diagnostics,
+                                         &feature_flag_values));
+  EXPECT_THAT(feature_flag_values,
+              UnorderedElementsAre(Pair("foo", std::optional<bool>(true)),
+                                   Pair("bar", std::optional<bool>(false)),
+                                   Pair("baz", std::nullopt), Pair("quux", std::nullopt)));
+}
+
 TEST (UtilTest, AdjustSplitConstraintsForMinSdk) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
 
diff --git a/tools/aapt2/link/FeatureFlagsFilter.cpp b/tools/aapt2/link/FeatureFlagsFilter.cpp
new file mode 100644
index 0000000..fdf3f74
--- /dev/null
+++ b/tools/aapt2/link/FeatureFlagsFilter.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#include "link/FeatureFlagsFilter.h"
+
+#include <string_view>
+
+#include "androidfw/IDiagnostics.h"
+#include "androidfw/Source.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlUtil.h"
+
+using ::aapt::xml::Element;
+using ::aapt::xml::Node;
+using ::aapt::xml::NodeCast;
+
+namespace aapt {
+
+class FlagsVisitor : public xml::Visitor {
+ public:
+  explicit FlagsVisitor(android::IDiagnostics* diagnostics,
+                        const FeatureFlagValues& feature_flag_values,
+                        const FeatureFlagsFilterOptions& options)
+      : diagnostics_(diagnostics), feature_flag_values_(feature_flag_values), options_(options) {
+  }
+
+  void Visit(xml::Element* node) override {
+    std::erase_if(node->children,
+                  [this](std::unique_ptr<xml::Node>& node) { return ShouldRemove(node); });
+    VisitChildren(node);
+  }
+
+  bool HasError() const {
+    return has_error_;
+  }
+
+ private:
+  bool ShouldRemove(std::unique_ptr<xml::Node>& node) {
+    if (const auto* el = NodeCast<Element>(node.get())) {
+      auto* attr = el->FindAttribute(xml::kSchemaAndroid, "featureFlag");
+      if (attr == nullptr) {
+        return false;
+      }
+
+      bool negated = false;
+      std::string_view flag_name = util::TrimWhitespace(attr->value);
+      if (flag_name.starts_with('!')) {
+        negated = true;
+        flag_name = flag_name.substr(1);
+      }
+
+      if (auto it = feature_flag_values_.find(std::string(flag_name));
+          it != feature_flag_values_.end()) {
+        if (it->second.has_value()) {
+          if (options_.remove_disabled_elements) {
+            // Remove if flag==true && attr=="!flag" (negated) OR flag==false && attr=="flag"
+            return *it->second == negated;
+          }
+        } else if (options_.flags_must_have_value) {
+          diagnostics_->Error(android::DiagMessage(node->line_number)
+                              << "attribute 'android:featureFlag' has flag '" << flag_name
+                              << "' without a true/false value from --feature_flags parameter");
+          has_error_ = true;
+          return false;
+        }
+      } else if (options_.fail_on_unrecognized_flags) {
+        diagnostics_->Error(android::DiagMessage(node->line_number)
+                            << "attribute 'android:featureFlag' has flag '" << flag_name
+                            << "' not found in flags from --feature_flags parameter");
+        has_error_ = true;
+        return false;
+      }
+    }
+
+    return false;
+  }
+
+  android::IDiagnostics* diagnostics_;
+  const FeatureFlagValues& feature_flag_values_;
+  const FeatureFlagsFilterOptions& options_;
+  bool has_error_ = false;
+};
+
+bool FeatureFlagsFilter::Consume(IAaptContext* context, xml::XmlResource* doc) {
+  FlagsVisitor visitor(context->GetDiagnostics(), feature_flag_values_, options_);
+  doc->root->Accept(&visitor);
+  return !visitor.HasError();
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/link/FeatureFlagsFilter.h b/tools/aapt2/link/FeatureFlagsFilter.h
new file mode 100644
index 0000000..1d342a7
--- /dev/null
+++ b/tools/aapt2/link/FeatureFlagsFilter.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "android-base/macros.h"
+#include "cmd/Util.h"
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+struct FeatureFlagsFilterOptions {
+  // If true, elements whose featureFlag values are false (i.e., disabled feature) will be removed.
+  bool remove_disabled_elements = true;
+
+  // If true, `Consume()` will return false (error) if a flag was found that is not in
+  // `feature_flag_values`.
+  bool fail_on_unrecognized_flags = true;
+
+  // If true, `Consume()` will return false (error) if a flag was found whose value in
+  // `feature_flag_values` is not defined (std::nullopt).
+  bool flags_must_have_value = true;
+};
+
+// Looks for the `android:featureFlag` attribute in each XML element, validates the flag names and
+// values, and removes elements according to the values in `feature_flag_values`. An element will be
+// removed if the flag's given value is FALSE. A "!" before the flag name in the attribute indicates
+// a boolean NOT operation, i.e., an element will be removed if the flag's given value is TRUE. For
+// example, if the XML is the following:
+//
+//   <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+//     <permission android:name="FOO" android:featureFlag="!flag"
+//                 android:protectionLevel="normal" />
+//     <permission android:name="FOO" android:featureFlag="flag"
+//                 android:protectionLevel="dangerous" />
+//   </manifest>
+//
+// If `feature_flag_values` contains {"flag", true}, then the <permission> element with
+// protectionLevel="normal" will be removed, and the <permission> element with
+// protectionLevel="normal" will be kept.
+//
+// The `Consume()` function will return false if there is an invalid flag found (see
+// FeatureFlagsFilterOptions for customizing the filter's validation behavior). Do not use the XML
+// further if there are errors as there may be elements removed already.
+class FeatureFlagsFilter : public IXmlResourceConsumer {
+ public:
+  explicit FeatureFlagsFilter(FeatureFlagValues feature_flag_values,
+                              FeatureFlagsFilterOptions options)
+      : feature_flag_values_(std::move(feature_flag_values)), options_(options) {
+  }
+
+  bool Consume(IAaptContext* context, xml::XmlResource* doc) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FeatureFlagsFilter);
+
+  const FeatureFlagValues feature_flag_values_;
+  const FeatureFlagsFilterOptions options_;
+};
+
+}  // namespace aapt
diff --git a/tools/aapt2/link/FeatureFlagsFilter_test.cpp b/tools/aapt2/link/FeatureFlagsFilter_test.cpp
new file mode 100644
index 0000000..53086cc
--- /dev/null
+++ b/tools/aapt2/link/FeatureFlagsFilter_test.cpp
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+#include "link/FeatureFlagsFilter.h"
+
+#include <string_view>
+
+#include "test/Test.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace aapt {
+
+// Returns null if there was an error from FeatureFlagsFilter.
+std::unique_ptr<xml::XmlResource> VerifyWithOptions(std::string_view str,
+                                                    const FeatureFlagValues& feature_flag_values,
+                                                    const FeatureFlagsFilterOptions& options) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(str);
+  FeatureFlagsFilter filter(feature_flag_values, options);
+  if (filter.Consume(test::ContextBuilder().Build().get(), doc.get())) {
+    return doc;
+  }
+  return {};
+}
+
+// Returns null if there was an error from FeatureFlagsFilter.
+std::unique_ptr<xml::XmlResource> Verify(std::string_view str,
+                                         const FeatureFlagValues& feature_flag_values) {
+  return VerifyWithOptions(str, feature_flag_values, {});
+}
+
+TEST(FeatureFlagsFilterTest, NoFeatureFlagAttributes) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" />
+    </manifest>)EOF",
+                    {{"flag", false}});
+  ASSERT_THAT(doc, NotNull());
+  auto root = doc->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+TEST(FeatureFlagsFilterTest, RemoveElementWithDisabledFlag) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag="flag" />
+    </manifest>)EOF",
+                    {{"flag", false}});
+  ASSERT_THAT(doc, NotNull());
+  auto root = doc->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, RemoveElementWithNegatedEnabledFlag) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag="!flag" />
+    </manifest>)EOF",
+                    {{"flag", true}});
+  ASSERT_THAT(doc, NotNull());
+  auto root = doc->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, KeepElementWithEnabledFlag) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag="flag" />
+    </manifest>)EOF",
+                    {{"flag", true}});
+  ASSERT_THAT(doc, NotNull());
+  auto root = doc->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST(FeatureFlagsFilterTest, SideBySideEnabledAndDisabled) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag="!flag"
+                  android:protectionLevel="normal" />
+      <permission android:name="FOO" android:featureFlag="flag"
+                  android:protectionLevel="dangerous" />
+    </manifest>)EOF",
+                    {{"flag", true}});
+  ASSERT_THAT(doc, NotNull());
+  auto root = doc->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto children = root->GetChildElements();
+  ASSERT_EQ(children.size(), 1);
+  auto attr = children[0]->FindAttribute(xml::kSchemaAndroid, "protectionLevel");
+  ASSERT_THAT(attr, NotNull());
+  ASSERT_EQ(attr->value, "dangerous");
+}
+
+TEST(FeatureFlagsFilterTest, RemoveDeeplyNestedElement) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <application>
+        <provider />
+        <activity>
+          <layout android:featureFlag="!flag" />
+        </activity>
+      </application>
+    </manifest>)EOF",
+                    {{"flag", true}});
+  ASSERT_THAT(doc, NotNull());
+  auto root = doc->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto application = root->FindChild({}, "application");
+  ASSERT_THAT(application, NotNull());
+  auto activity = application->FindChild({}, "activity");
+  ASSERT_THAT(activity, NotNull());
+  auto maybe_removed = activity->FindChild({}, "layout");
+  ASSERT_THAT(maybe_removed, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, KeepDeeplyNestedElement) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <application>
+        <provider />
+        <activity>
+          <layout android:featureFlag="flag" />
+        </activity>
+      </application>
+    </manifest>)EOF",
+                    {{"flag", true}});
+  ASSERT_THAT(doc, NotNull());
+  auto root = doc->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto application = root->FindChild({}, "application");
+  ASSERT_THAT(application, NotNull());
+  auto activity = application->FindChild({}, "activity");
+  ASSERT_THAT(activity, NotNull());
+  auto maybe_removed = activity->FindChild({}, "layout");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST(FeatureFlagsFilterTest, FailOnEmptyFeatureFlagAttribute) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag=" " />
+    </manifest>)EOF",
+                    {{"flag", false}});
+  ASSERT_THAT(doc, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, FailOnFlagWithNoGivenValue) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag="flag" />
+    </manifest>)EOF",
+                    {{"flag", std::nullopt}});
+  ASSERT_THAT(doc, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, FailOnUnrecognizedFlag) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag="unrecognized" />
+    </manifest>)EOF",
+                    {{"flag", true}});
+  ASSERT_THAT(doc, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, FailOnMultipleValidationErrors) {
+  auto doc = Verify(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag="bar" />
+      <permission android:name="FOO" android:featureFlag="unrecognized" />
+    </manifest>)EOF",
+                    {{"flag", std::nullopt}});
+  ASSERT_THAT(doc, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, OptionRemoveDisabledElementsIsFalse) {
+  auto doc = VerifyWithOptions(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag="flag" />
+    </manifest>)EOF",
+                               {{"flag", false}}, {.remove_disabled_elements = false});
+  ASSERT_THAT(doc, NotNull());
+  auto root = doc->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST(FeatureFlagsFilterTest, OptionFlagsMustHaveValueIsFalse) {
+  auto doc = VerifyWithOptions(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag="flag" />
+    </manifest>)EOF",
+                               {{"flag", std::nullopt}}, {.flags_must_have_value = false});
+  ASSERT_THAT(doc, NotNull());
+  auto root = doc->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST(FeatureFlagsFilterTest, OptionFailOnUnrecognizedFlagsIsFalse) {
+  auto doc = VerifyWithOptions(R"EOF(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+      <permission android:name="FOO" android:featureFlag="unrecognized" />
+    </manifest>)EOF",
+                               {{"flag", true}}, {.fail_on_unrecognized_flags = false});
+  ASSERT_THAT(doc, NotNull());
+  auto root = doc->root.get();
+  ASSERT_THAT(root, NotNull());
+  auto maybe_removed = root->FindChild({}, "permission");
+  ASSERT_THAT(maybe_removed, NotNull());
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index 44cd276..18165f7 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -20,12 +20,12 @@
 #include <set>
 #include <unordered_set>
 
-#include "android-base/macros.h"
-#include "androidfw/ConfigDescription.h"
-#include "androidfw/StringPiece.h"
-
 #include "Resource.h"
 #include "SdkConstants.h"
+#include "android-base/macros.h"
+#include "android-base/result.h"
+#include "androidfw/ConfigDescription.h"
+#include "androidfw/StringPiece.h"
 #include "process/IResourceTableConsumer.h"
 #include "xml/XmlDom.h"
 
@@ -92,28 +92,6 @@
   DISALLOW_COPY_AND_ASSIGN(PrivateAttributeMover);
 };
 
-class ResourceConfigValue;
-
-class ProductFilter : public IResourceTableConsumer {
- public:
-  using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
-
-  explicit ProductFilter(std::unordered_set<std::string> products) : products_(products) {
-  }
-
-  ResourceConfigValueIter SelectProductToKeep(const ResourceNameRef& name,
-                                              const ResourceConfigValueIter begin,
-                                              const ResourceConfigValueIter end,
-                                              android::IDiagnostics* diag);
-
-  bool Consume(IAaptContext* context, ResourceTable* table) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ProductFilter);
-
-  std::unordered_set<std::string> products_;
-};
-
 // Removes namespace nodes and URI information from the XmlResource.
 //
 // Once an XmlResource is processed by this consumer, it is no longer able to have its attributes
diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp
deleted file mode 100644
index 2cb9afa..0000000
--- a/tools/aapt2/link/ProductFilter_test.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#include "link/Linkers.h"
-
-#include "test/Test.h"
-
-using ::android::ConfigDescription;
-
-namespace aapt {
-
-TEST(ProductFilterTest, SelectTwoProducts) {
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-
-  const ConfigDescription land = test::ParseConfigOrDie("land");
-  const ConfigDescription port = test::ParseConfigOrDie("port");
-
-  ResourceTable table;
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(),
-                    land)
-          .Build(),
-      context->GetDiagnostics()));
-
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/tablet.xml")).Build(),
-                    land, "tablet")
-          .Build(),
-      context->GetDiagnostics()));
-
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(),
-                    port)
-          .Build(),
-      context->GetDiagnostics()));
-
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/tablet.xml")).Build(),
-                    port, "tablet")
-          .Build(),
-      context->GetDiagnostics()));
-
-  ProductFilter filter({"tablet"});
-  ASSERT_TRUE(filter.Consume(context.get(), &table));
-
-  EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(
-                         &table, "android:string/one", land, ""));
-  EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
-                         &table, "android:string/one", land, "tablet"));
-  EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(
-                         &table, "android:string/one", port, ""));
-  EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
-                         &table, "android:string/one", port, "tablet"));
-}
-
-TEST(ProductFilterTest, SelectDefaultProduct) {
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-
-  ResourceTable table;
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build())
-          .Build(),
-      context->GetDiagnostics()));
-
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("tablet.xml")).Build(), {},
-                    "tablet")
-          .Build(),
-      context->GetDiagnostics()));
-  ;
-
-  ProductFilter filter(std::unordered_set<std::string>{});
-  ASSERT_TRUE(filter.Consume(context.get(), &table));
-
-  EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
-                         &table, "android:string/one",
-                         ConfigDescription::DefaultConfig(), ""));
-  EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(
-                         &table, "android:string/one",
-                         ConfigDescription::DefaultConfig(), "tablet"));
-}
-
-TEST(ProductFilterTest, FailOnAmbiguousProduct) {
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-
-  ResourceTable table;
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build())
-          .Build(),
-      context->GetDiagnostics()));
-
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("tablet.xml")).Build(), {},
-                    "tablet")
-          .Build(),
-      context->GetDiagnostics()));
-
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("no-sdcard.xml")).Build(),
-                    {}, "no-sdcard")
-          .Build(),
-      context->GetDiagnostics()));
-
-  ProductFilter filter({"tablet", "no-sdcard"});
-  ASSERT_FALSE(filter.Consume(context.get(), &table));
-}
-
-TEST(ProductFilterTest, FailOnMultipleDefaults) {
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-
-  ResourceTable table;
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source(".xml")).Build())
-          .Build(),
-      context->GetDiagnostics()));
-
-  ASSERT_TRUE(table.AddResource(
-      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
-          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build(), {},
-                    "default")
-          .Build(),
-      context->GetDiagnostics()));
-
-  ProductFilter filter(std::unordered_set<std::string>{});
-  ASSERT_FALSE(filter.Consume(context.get(), &table));
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/process/ProductFilter.cpp
similarity index 60%
rename from tools/aapt2/link/ProductFilter.cpp
rename to tools/aapt2/process/ProductFilter.cpp
index 9544986..0b1c0a6 100644
--- a/tools/aapt2/link/ProductFilter.cpp
+++ b/tools/aapt2/process/ProductFilter.cpp
@@ -14,16 +14,18 @@
  * limitations under the License.
  */
 
-#include "link/Linkers.h"
+#include "process/ProductFilter.h"
+
+#include <algorithm>
 
 #include "ResourceTable.h"
 #include "trace/TraceBuffer.h"
 
 namespace aapt {
 
-ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep(
-    const ResourceNameRef& name, const ResourceConfigValueIter begin,
-    const ResourceConfigValueIter end, android::IDiagnostics* diag) {
+std::optional<ProductFilter::ResourceConfigValueIter> ProductFilter::SelectProductToKeep(
+    const ResourceNameRef& name, ResourceConfigValueIter begin, ResourceConfigValueIter end,
+    android::IDiagnostics* diag) {
   ResourceConfigValueIter default_product_iter = end;
   ResourceConfigValueIter selected_product_iter = end;
 
@@ -36,12 +38,11 @@
                     << "selection of product '" << config_value->product << "' for resource "
                     << name << " is ambiguous");
 
-        ResourceConfigValue* previously_selected_config_value =
-            selected_product_iter->get();
+        ResourceConfigValue* previously_selected_config_value = selected_product_iter->get();
         diag->Note(android::DiagMessage(previously_selected_config_value->value->GetSource())
                    << "product '" << previously_selected_config_value->product
                    << "' is also a candidate");
-        return end;
+        return std::nullopt;
       }
 
       // Select this product.
@@ -54,11 +55,10 @@
         diag->Error(android::DiagMessage(config_value->value->GetSource())
                     << "multiple default products defined for resource " << name);
 
-        ResourceConfigValue* previously_default_config_value =
-            default_product_iter->get();
+        ResourceConfigValue* previously_default_config_value = default_product_iter->get();
         diag->Note(android::DiagMessage(previously_default_config_value->value->GetSource())
                    << "default product also defined here");
-        return end;
+        return std::nullopt;
       }
 
       // Mark the default.
@@ -66,9 +66,16 @@
     }
   }
 
+  if (remove_default_config_values_) {
+    // If we are leaving only a specific product, return early here instead of selecting the default
+    // value. Returning end here will cause this value set to be skipped, and will be removed with
+    // ClearEmptyValues method.
+    return selected_product_iter;
+  }
+
   if (default_product_iter == end) {
     diag->Error(android::DiagMessage() << "no default product defined for resource " << name);
-    return end;
+    return std::nullopt;
   }
 
   if (selected_product_iter == end) {
@@ -89,20 +96,27 @@
         ResourceConfigValueIter start_range_iter = iter;
         while (iter != entry->values.end()) {
           ++iter;
-          if (iter == entry->values.end() ||
-              (*iter)->config != (*start_range_iter)->config) {
+          if (iter == entry->values.end() || (*iter)->config != (*start_range_iter)->config) {
             // End of the array, or we saw a different config,
             // so this must be the end of a range of products.
             // Select the product to keep from the set of products defined.
             ResourceNameRef name(pkg->name, type->named_type, entry->name);
-            auto value_to_keep = SelectProductToKeep(
-                name, start_range_iter, iter, context->GetDiagnostics());
-            if (value_to_keep == iter) {
+            auto value_to_keep =
+                SelectProductToKeep(name, start_range_iter, iter, context->GetDiagnostics());
+            if (!value_to_keep.has_value()) {
               // An error occurred, we could not pick a product.
               error = true;
-            } else {
+            } else if (auto val = value_to_keep.value(); val != iter) {
               // We selected a product to keep. Move it to the new array.
-              new_values.push_back(std::move(*value_to_keep));
+              if (remove_default_config_values_) {
+                // We are filtering values with the given product. The selected value here will be
+                // a new default value, and all other values will be removed.
+                new_values.push_back(
+                    std::make_unique<ResourceConfigValue>((*val)->config, android::StringPiece{}));
+                new_values.back()->value = std::move((*val)->value);
+              } else {
+                new_values.push_back(std::move(*val));
+              }
             }
 
             // Start the next range of products.
@@ -115,7 +129,27 @@
       }
     }
   }
+
+  if (remove_default_config_values_) {
+    ClearEmptyValues(table);
+  }
+
   return !error;
 }
 
+void ProductFilter::ClearEmptyValues(ResourceTable* table) {
+  // Clear any empty packages/types/entries, as remove_default_config_values_ may remove an entire
+  // value set.
+  CHECK(remove_default_config_values_)
+      << __func__ << " should only be called when remove_default_config_values_ is set";
+
+  for (auto& pkg : table->packages) {
+    for (auto& type : pkg->types) {
+      std::erase_if(type->entries, [](auto& entry) { return entry->values.empty(); });
+    }
+    std::erase_if(pkg->types, [](auto& type) { return type->entries.empty(); });
+  }
+  std::erase_if(table->packages, [](auto& package) { return package->types.empty(); });
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/process/ProductFilter.h b/tools/aapt2/process/ProductFilter.h
new file mode 100644
index 0000000..0ec2f00
--- /dev/null
+++ b/tools/aapt2/process/ProductFilter.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "Resource.h"
+#include "android-base/macros.h"
+#include "androidfw/ConfigDescription.h"
+#include "androidfw/IDiagnostics.h"
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+class ResourceConfigValue;
+
+class ProductFilter : public IResourceTableConsumer {
+ public:
+  using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
+
+  // Setting remove_default_config_values will remove all values other than
+  // specified product, including default. For example, if the following table
+  //
+  //     <string name="foo" product="default">foo_default</string>
+  //     <string name="foo" product="tablet">foo_tablet</string>
+  //     <string name="bar">bar</string>
+  //
+  // is consumed with tablet, it will result in
+  //
+  //     <string name="foo">foo_tablet</string>
+  //
+  // removing foo_default and bar. This option is to generate an RRO package
+  // with given product.
+  explicit ProductFilter(std::unordered_set<std::string> products,
+                         bool remove_default_config_values)
+      : products_(std::move(products)),
+        remove_default_config_values_(remove_default_config_values) {
+  }
+
+  bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProductFilter);
+
+  // SelectProductToKeep returns an iterator for the selected value.
+  //
+  // Returns std::nullopt in case of failure (e.g. ambiguous values, missing or duplicated default
+  // values).
+  // Returns `end` if keep_as_default_product is set and no value for the specified product was
+  // found.
+  std::optional<ResourceConfigValueIter> SelectProductToKeep(const ResourceNameRef& name,
+                                                             ResourceConfigValueIter begin,
+                                                             ResourceConfigValueIter end,
+                                                             android::IDiagnostics* diag);
+
+  void ClearEmptyValues(ResourceTable* table);
+
+  std::unordered_set<std::string> products_;
+  bool remove_default_config_values_;
+};
+
+}  // namespace aapt
diff --git a/tools/aapt2/process/ProductFilter_test.cpp b/tools/aapt2/process/ProductFilter_test.cpp
new file mode 100644
index 0000000..27a82dc
--- /dev/null
+++ b/tools/aapt2/process/ProductFilter_test.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "process/ProductFilter.h"
+
+#include "test/Test.h"
+
+using ::android::ConfigDescription;
+
+namespace aapt {
+
+TEST(ProductFilterTest, SelectTwoProducts) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  const ConfigDescription land = test::ParseConfigOrDie("land");
+  const ConfigDescription port = test::ParseConfigOrDie("port");
+
+  ResourceTable table;
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(),
+                    land)
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/tablet.xml")).Build(),
+                    land, "tablet")
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(),
+                    port)
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/tablet.xml")).Build(),
+                    port, "tablet")
+          .Build(),
+      context->GetDiagnostics()));
+
+  ProductFilter filter({"tablet"}, /* remove_default_config_values = */ false);
+  ASSERT_TRUE(filter.Consume(context.get(), &table));
+
+  EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", land, ""));
+  EXPECT_NE(nullptr,
+            test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", land, "tablet"));
+  EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", port, ""));
+  EXPECT_NE(nullptr,
+            test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", port, "tablet"));
+}
+
+TEST(ProductFilterTest, SelectDefaultProduct) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  ResourceTable table;
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build())
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("tablet.xml")).Build(), {},
+                    "tablet")
+          .Build(),
+      context->GetDiagnostics()));
+  ;
+
+  ProductFilter filter(std::unordered_set<std::string>{},
+                       /* remove_default_config_values = */ false);
+  ASSERT_TRUE(filter.Consume(context.get(), &table));
+
+  EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one",
+                                                           ConfigDescription::DefaultConfig(), ""));
+  EXPECT_EQ(nullptr,
+            test::GetValueForConfigAndProduct<Id>(&table, "android:string/one",
+                                                  ConfigDescription::DefaultConfig(), "tablet"));
+}
+
+TEST(ProductFilterTest, FailOnAmbiguousProduct) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  ResourceTable table;
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build())
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("tablet.xml")).Build(), {},
+                    "tablet")
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("no-sdcard.xml")).Build(),
+                    {}, "no-sdcard")
+          .Build(),
+      context->GetDiagnostics()));
+
+  ProductFilter filter({"tablet", "no-sdcard"}, /* remove_default_config_values = */ false);
+  ASSERT_FALSE(filter.Consume(context.get(), &table));
+}
+
+TEST(ProductFilterTest, FailOnMultipleDefaults) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  ResourceTable table;
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source(".xml")).Build())
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build(), {},
+                    "default")
+          .Build(),
+      context->GetDiagnostics()));
+
+  ProductFilter filter(std::unordered_set<std::string>{},
+                       /* remove_default_config_values = */ false);
+  ASSERT_FALSE(filter.Consume(context.get(), &table));
+}
+
+TEST(ProductFilterTest, RemoveDefaultConfigValues) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  const ConfigDescription land = test::ParseConfigOrDie("land");
+  const ConfigDescription port = test::ParseConfigOrDie("port");
+
+  ResourceTable table;
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(),
+                    land)
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/tablet.xml")).Build(),
+                    land, "tablet")
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/two"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(),
+                    land)
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(),
+                    port)
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/tablet.xml")).Build(),
+                    port, "tablet")
+          .Build(),
+      context->GetDiagnostics()));
+
+  ASSERT_TRUE(table.AddResource(
+      NewResourceBuilder(test::ParseNameOrDie("android:string/two"))
+          .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(),
+                    port)
+          .Build(),
+      context->GetDiagnostics()));
+
+  ProductFilter filter({"tablet"}, /* remove_default_config_values = */ true);
+  ASSERT_TRUE(filter.Consume(context.get(), &table));
+
+  EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", land, ""));
+  EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/two", land, ""));
+  EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", port, ""));
+  EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/two", port, ""));
+}
+
+}  // namespace aapt
diff --git a/tools/fonts/update_font_metadata.py b/tools/fonts/update_font_metadata.py
index c07a98a..04a5528 100755
--- a/tools/fonts/update_font_metadata.py
+++ b/tools/fonts/update_font_metadata.py
@@ -19,7 +19,7 @@
     args_parser.add_argument('--revision', help='Updated font revision. Use + to update revision based on the current revision')
     args = args_parser.parse_args()
 
-    font = ttLib.TTFont(args.input)
+    font = ttLib.TTFont(args.input, recalcTimestamp=False)
     update_font_revision(font, args.revision)
     font.save(args.output)
 
diff --git a/tools/lint/README.md b/tools/lint/README.md
index b235ad6..ff8e442 100644
--- a/tools/lint/README.md
+++ b/tools/lint/README.md
@@ -103,10 +103,15 @@
 
 As noted above, this baseline file contains warnings too, which might be undesirable. For example,
 CI tools might surface these warnings in code reviews. In order to create this file without
-warnings, we need to pass another flag to lint: `--nowarn`. The easiest way to do this is to
-locally change the soong code in
-[lint.go](http://cs/aosp-master/build/soong/java/lint.go;l=451;rcl=2e778d5bc4a8d1d77b4f4a3029a4a254ad57db75)
-adding `cmd.Flag("--nowarn")` and running lint again.
+warnings, we need to pass another flag to lint: `--nowarn`. One option is to add the flag to your
+Android.bp file and then run lint again:
+
+```
+  lint: {
+    extra_check_modules: ["AndroidFrameworkLintChecker"],
+    flags: ["--nowarn"],
+  }
+```
 
 # Documentation
 
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/Constants.kt
index e03d92ab..f1727b7 100644
--- a/tools/lint/common/src/main/java/com/google/android/lint/aidl/Constants.kt
+++ b/tools/lint/common/src/main/java/com/google/android/lint/aidl/Constants.kt
@@ -30,6 +30,7 @@
 const val IINTERFACE_INTERFACE = "android.os.IInterface"
 
 const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission"
+const val PERMISSION_PREFIX_LITERAL = "android.permission."
 
 /**
  * If a non java (e.g. c++) backend is enabled, the @EnforcePermission
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
index d41fee3..24d203f 100644
--- a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
+++ b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
@@ -24,33 +24,31 @@
 import org.jetbrains.uast.UMethod
 
 /**
- * Given a UMethod, determine if this method is
- * the entrypoint to an interface generated by AIDL,
- * returning the interface name if so, otherwise returning null
+ * Given a UMethod, determine if this method is the entrypoint to an interface
+ * generated by AIDL, returning the interface name if so, otherwise returning
+ * null
  */
 fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? {
-    if (!isContainedInSubclassOfStub(context, node)) return null
-    for (superMethod in node.findSuperMethods()) {
-        for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
-            ?: continue) {
-            if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
-                return superMethod.containingClass?.name
-            }
-        }
+    val containingStub = containingStub(context, node) ?: return null
+    val superMethod = node.findSuperMethods(containingStub)
+    if (superMethod.isEmpty()) return null
+    return containingStub.containingClass?.name
+}
+
+/* Returns the containing Stub class if any. This is not sufficient to infer
+ * that the method itself extends an AIDL generated method. See
+ * getContainingAidlInterface for that purpose.
+ */
+fun containingStub(context: JavaContext, node: UMethod?): PsiClass? {
+    var superClass = node?.containingClass?.superClass
+    while (superClass != null) {
+        if (isStub(context, superClass)) return superClass
+        superClass = superClass.superClass
     }
     return null
 }
 
-fun isContainedInSubclassOfStub(context: JavaContext, node: UMethod?): Boolean {
-    var superClass = node?.containingClass?.superClass
-    while (superClass != null) {
-        if (isStub(context, superClass)) return true
-        superClass = superClass.superClass
-    }
-    return false
-}
-
-fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean {
+private fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean {
     if (psiClass == null) return false
     if (psiClass.name != "Stub") return false
     if (!context.evaluator.isStatic(psiClass)) return false
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 935bade..624a198 100644
--- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -20,6 +20,7 @@
 import com.android.tools.lint.client.api.Vendor
 import com.android.tools.lint.detector.api.CURRENT_API
 import com.google.android.lint.parcel.SaferParcelChecker
+import com.google.android.lint.aidl.PermissionAnnotationDetector
 import com.google.auto.service.AutoService
 
 @AutoService(IssueRegistry::class)
@@ -37,6 +38,7 @@
         SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
         // TODO: Currently crashes due to OOM issue
         // PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
+        PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION,
         PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE,
         PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD,
     )
diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt
new file mode 100644
index 0000000..6b50cfd
--- /dev/null
+++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UMethod
+
+/**
+ * Ensures all AIDL-generated methods are annotated.
+ *
+ * This detector is run on system_server to validate that any method that may
+ * be exposed via an AIDL interface is permission-annotated. That is, it must
+ * have one of the following annotation:
+ *   - @EnforcePermission
+ *   - @RequiresNoPermission
+ *   - @PermissionManuallyEnforced
+ */
+class PermissionAnnotationDetector : AidlImplementationDetector() {
+
+    override fun visitAidlMethod(
+      context: JavaContext,
+      node: UMethod,
+      interfaceName: String,
+      body: UBlockExpression
+    ) {
+        if (context.evaluator.isAbstract(node)) return
+
+        if (AIDL_PERMISSION_ANNOTATIONS.any { node.hasAnnotation(it) }) return
+
+        context.report(
+            ISSUE_MISSING_PERMISSION_ANNOTATION,
+            node,
+            context.getLocation(node),
+            "The method ${node.name} is not permission-annotated."
+        )
+    }
+
+    companion object {
+
+        private val EXPLANATION_MISSING_PERMISSION_ANNOTATION = """
+          Interfaces that are exposed by system_server are required to have an annotation which
+          denotes the type of permission enforced. There are 3 possible options:
+            - @EnforcePermission
+            - @RequiresNoPermission
+            - @PermissionManuallyEnforced
+          See the documentation of each annotation for further details.
+
+          The annotation on the Java implementation must be the same that the AIDL interface
+          definition. This is verified by a lint in the build system.
+          """.trimIndent()
+
+        @JvmField
+        val ISSUE_MISSING_PERMISSION_ANNOTATION = Issue.create(
+            id = "MissingPermissionAnnotation",
+            briefDescription = "No permission annotation on exposed AIDL interface.",
+            explanation = EXPLANATION_MISSING_PERMISSION_ANNOTATION,
+            category = Category.CORRECTNESS,
+            priority = 5,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                PermissionAnnotationDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            ),
+            enabledByDefault = false
+        )
+    }
+}
diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt
new file mode 100644
index 0000000..bce848a
--- /dev/null
+++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class PermissionAnnotationDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = PermissionAnnotationDetector()
+
+    override fun getIssues(): List<Issue> = listOf(
+        PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION,
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    /** No issue scenario */
+
+    fun testDoesNotDetectIssuesInCorrectScenario() {
+        lint().files(
+            java(
+            """
+            public class Foo extends IFoo.Stub {
+                @Override
+                @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+                public void testMethod() { }
+            }
+            """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testMissingAnnotation() {
+        lint().files(
+            java(
+            """
+            public class Bar extends IBar.Stub {
+                public void testMethod() { }
+            }
+            """
+            ).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/Bar.java:2: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation]
+                    public void testMethod() { }
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """
+            )
+    }
+
+    fun testNoIssueWhenExtendingWithAnotherSubclass() {
+        lint().files(
+            java(
+            """
+            public class Foo extends IFoo.Stub {
+                @Override
+                @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                public void testMethod() { }
+                // not an AIDL method, just another method
+                public void someRandomMethod() { }
+            }
+            """).indented(),
+            java(
+            """
+            public class Baz extends Bar {
+              @Override
+              public void someRandomMethod() { }
+            }
+            """).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    /* Stubs */
+
+    // A service with permission annotation on the method.
+    private val interfaceIFoo: TestFile = java(
+        """
+        public interface IFoo extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IFoo {
+          }
+          @Override
+          @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+          public void testMethod();
+          @Override
+          @android.annotation.RequiresNoPermission
+          public void testMethodNoPermission();
+          @Override
+          @android.annotation.PermissionManuallyEnforced
+          public void testMethodManual();
+        }
+        """
+    ).indented()
+
+    // A service with no permission annotation.
+    private val interfaceIBar: TestFile = java(
+        """
+        public interface IBar extends android.os.IInterface {
+         public static abstract class Stub extends android.os.Binder implements IBar {
+          }
+          public void testMethod();
+        }
+        """
+    ).indented()
+
+    private val stubs = arrayOf(interfaceIFoo, interfaceIBar)
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
index a20266a..28eab8f 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
@@ -20,7 +20,6 @@
 import com.android.tools.lint.client.api.Vendor
 import com.android.tools.lint.detector.api.CURRENT_API
 import com.google.android.lint.aidl.EnforcePermissionDetector
-import com.google.android.lint.aidl.EnforcePermissionHelperDetector
 import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
 import com.google.auto.service.AutoService
 
@@ -30,7 +29,8 @@
     override val issues = listOf(
             EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
             EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
-            EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+            EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+            EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION,
             SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
     )
 
@@ -45,4 +45,4 @@
             feedbackUrl = "http://b/issues/new?component=315013",
             contact = "repsonsible-apis@google.com"
     )
-}
\ No newline at end of file
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 0baac2c..4455a9c 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -30,29 +30,34 @@
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.google.android.lint.findCallExpression
 import com.intellij.psi.PsiAnnotation
 import com.intellij.psi.PsiArrayInitializerMemberValue
 import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiMethod
-import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UDeclarationsExpression
 import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
 import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.toUElement
+import org.jetbrains.uast.skipParenthesizedExprDown
+
+import java.util.EnumSet
 
 /**
- * Lint Detector that ensures that any method overriding a method annotated
- * with @EnforcePermission is also annotated with the exact same annotation.
- * The intent is to surface the effective permission checks to the service
- * implementations.
+ * Lint Detector that ensures consistency when using the @EnforcePermission
+ * annotation. Multiple verifications are implemented:
  *
- * This is done with 2 mechanisms:
  *  1. Visit any annotation usage, to ensure that any derived class will have
- *     the correct annotation on each methods. This is for the top to bottom
- *     propagation.
- *  2. Visit any annotation, to ensure that if a method is annotated, it has
+ *     the correct annotation on each methods. Even if the subclass does not
+ *     have the annotation, visitAnnotationUsage will be called which allows us
+ *     to capture the issue.
+ *  2. Visit any method, to ensure that if a method is annotated, it has
  *     its ancestor also annotated. This is to avoid having an annotation on a
  *     Java method without the corresponding annotation on the AIDL interface.
+ *  3. When annotated, ensures that the first instruction is to call the helper
+ *     method (or the parent helper).
  */
 class EnforcePermissionDetector : Detector(), SourceCodeScanner {
 
@@ -60,9 +65,8 @@
         return listOf(ANNOTATION_ENFORCE_PERMISSION)
     }
 
-    override fun getApplicableUastTypes(): List<Class<out UElement>> {
-        return listOf(UAnnotation::class.java)
-    }
+    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+            listOf(UMethod::class.java)
 
     private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> {
         if (elem is PsiArrayInitializerMemberValue)
@@ -93,7 +97,7 @@
             val v1 = ConstantEvaluator.evaluate(context, value1)
             val v2 = ConstantEvaluator.evaluate(context, value2)
             if (v1 != null && v2 != null) {
-                if (v1 != v2) {
+                if (v1 != v2 && !isOneShortPermissionOfOther(v1, v2)) {
                     return false
                 }
             } else {
@@ -105,7 +109,7 @@
                 for (j in children1.indices) {
                     val c1 = ConstantEvaluator.evaluate(context, children1[j])
                     val c2 = ConstantEvaluator.evaluate(context, children2[j])
-                    if (c1 != c2) {
+                    if (c1 != c2 && !isOneShortPermissionOfOther(c1, c2)) {
                         return false
                     }
                 }
@@ -114,6 +118,12 @@
         return true
     }
 
+    private fun isOneShortPermissionOfOther(
+        permission1: Any?,
+        permission2: Any?
+    ): Boolean = permission1 == (permission2 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL) ||
+            permission2 == (permission1 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL)
+
     private fun compareMethods(
         context: JavaContext,
         element: UElement,
@@ -121,11 +131,6 @@
         overriddenMethod: PsiMethod,
         checkEquivalence: Boolean = true
     ) {
-        // If method is not from a Stub subclass, this method shouldn't use @EP at all.
-        // This is handled by EnforcePermissionHelperDetector.
-        if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) {
-            return
-        }
         val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
         val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
         val location = context.getLocation(element)
@@ -161,40 +166,112 @@
     ) {
         if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
             annotationInfo.origin == AnnotationOrigin.METHOD) {
+            /* Ignore implementations that are not a sub-class of Stub (i.e., Proxy). */
+            val uMethod = element as? UMethod ?: return
+            if (getContainingAidlInterface(context, uMethod) == null) {
+                return
+            }
             val overridingMethod = element.sourcePsi as PsiMethod
             val overriddenMethod = usageInfo.referenced as PsiMethod
             compareMethods(context, element, overridingMethod, overriddenMethod)
         }
     }
 
-    override fun createUastHandler(context: JavaContext): UElementHandler {
-        return object : UElementHandler() {
-            override fun visitAnnotation(node: UAnnotation) {
-                if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) {
-                    return
-                }
-                val method = node.uastParent as? UMethod ?: return
-                val overridingMethod = method as PsiMethod
-                val parents = overridingMethod.findSuperMethods()
-                for (overriddenMethod in parents) {
-                    // The equivalence check can be skipped, if both methods are
-                    // annotated, it will be verified by visitAnnotationUsage.
-                    compareMethods(context, method, overridingMethod,
-                        overriddenMethod, checkEquivalence = false)
-                }
+    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+        override fun visitMethod(node: UMethod) {
+            if (context.evaluator.isAbstract(node)) return
+            if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
+
+            val stubClass = containingStub(context, node)
+            if (stubClass == null) {
+                context.report(
+                    ISSUE_MISUSING_ENFORCE_PERMISSION,
+                    node,
+                    context.getLocation(node),
+                    "The class of ${node.name} does not inherit from an AIDL generated Stub class"
+                )
+                return
+            }
+
+            /* Check that we are connected to the super class */
+            val overridingMethod = node as PsiMethod
+            val parents = overridingMethod.findSuperMethods(stubClass)
+            if (parents.isEmpty()) {
+                context.report(
+                    ISSUE_MISUSING_ENFORCE_PERMISSION,
+                    node,
+                    context.getLocation(node),
+                    "The method ${node.name} does not override an AIDL generated method"
+                )
+                return
+            }
+            for (overriddenMethod in parents) {
+                // The equivalence check can be skipped, if both methods are
+                // annotated, it will be verified by visitAnnotationUsage.
+                compareMethods(context, node, overridingMethod,
+                    overriddenMethod, checkEquivalence = false)
+            }
+
+            /* Check that the helper is called as a first instruction */
+            val targetExpression = getHelperMethodCallSourceString(node)
+            val message =
+                "Method must start with $targetExpression or super.${node.name}(), if applicable"
+
+            val firstExpression = (node.uastBody as? UBlockExpression)
+                    ?.expressions?.firstOrNull()
+
+            if (firstExpression == null) {
+                context.report(
+                    ISSUE_ENFORCE_PERMISSION_HELPER,
+                    context.getLocation(node),
+                    message,
+                )
+                return
+            }
+
+            val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
+              .asSourceString()
+              .filterNot(Char::isWhitespace)
+
+            if (firstExpressionSource != targetExpression &&
+                  firstExpressionSource != "super.$targetExpression") {
+                // calling super.<methodName>() is also legal
+                val directSuper = context.evaluator.getSuperMethod(node)
+                val firstCall = findCallExpression(firstExpression)?.resolve()
+                if (directSuper != null && firstCall == directSuper) return
+
+                val locationTarget = getLocationTarget(firstExpression)
+                val expressionLocation = context.getLocation(locationTarget)
+
+                context.report(
+                    ISSUE_ENFORCE_PERMISSION_HELPER,
+                    context.getLocation(node),
+                    message,
+                    getHelperMethodFix(node, expressionLocation),
+                )
             }
         }
     }
 
     companion object {
+
+        private const val HELPER_SUFFIX = "_enforcePermission"
+
         val EXPLANATION = """
-            The @EnforcePermission annotation is used to indicate that the underlying binder code
-            has already verified the caller's permissions before calling the appropriate method. The
-            verification code is usually generated by the AIDL compiler, which also takes care of
-            annotating the generated Java code.
+            The @EnforcePermission annotation is used to delegate the verification of the caller's
+            permissions to a generated AIDL method.
 
             In order to surface that information to platform developers, the same annotation must be
             used on the implementation class or methods.
+
+            The @EnforcePermission annotation can only be used on methods whose class extends from
+            the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
+            AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.
+
+            yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
+            either call it directly, or call it indirectly via super.yourMethodName().
             """
 
         val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create(
@@ -206,7 +283,7 @@
             severity = Severity.ERROR,
             implementation = Implementation(
                     EnforcePermissionDetector::class.java,
-                    Scope.JAVA_FILE_SCOPE
+                    EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
             )
         )
 
@@ -219,8 +296,47 @@
             severity = Severity.ERROR,
             implementation = Implementation(
                     EnforcePermissionDetector::class.java,
-                    Scope.JAVA_FILE_SCOPE
+                    EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
             )
         )
+
+        val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
+            id = "MissingEnforcePermissionHelper",
+            briefDescription = """Missing permission-enforcing method call in AIDL method
+                |annotated with @EnforcePermission""".trimMargin(),
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                    EnforcePermissionDetector::class.java,
+                    EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+            )
+        )
+
+        val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
+            id = "MisusingEnforcePermissionAnnotation",
+            briefDescription = "@EnforcePermission cannot be used here",
+            explanation = EXPLANATION,
+            category = Category.SECURITY,
+            priority = 6,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                    EnforcePermissionDetector::class.java,
+                    EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+            )
+        )
+
+        /**
+         * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
+         * resulting in an incorrect Location if used directly
+         */
+        private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
+            if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
+            if (firstExpression is UDeclarationsExpression) {
+                return firstExpression.declarations.firstOrNull()?.sourcePsi
+            }
+            return null
+        }
     }
 }
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
deleted file mode 100644
index df13af5..0000000
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2022 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.google.android.lint.aidl
-
-import com.android.tools.lint.client.api.UElementHandler
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.google.android.lint.findCallExpression
-import com.intellij.psi.PsiElement
-import org.jetbrains.uast.UBlockExpression
-import org.jetbrains.uast.UDeclarationsExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.skipParenthesizedExprDown
-
-class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
-    override fun getApplicableUastTypes(): List<Class<out UElement?>> =
-            listOf(UMethod::class.java)
-
-    override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
-
-    private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
-        override fun visitMethod(node: UMethod) {
-            if (context.evaluator.isAbstract(node)) return
-            if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
-
-            if (!isContainedInSubclassOfStub(context, node)) {
-                context.report(
-                    ISSUE_MISUSING_ENFORCE_PERMISSION,
-                    node,
-                    context.getLocation(node),
-                    "The class of ${node.name} does not inherit from an AIDL generated Stub class"
-                )
-                return
-            }
-
-            val targetExpression = getHelperMethodCallSourceString(node)
-            val message =
-                "Method must start with $targetExpression or super.${node.name}(), if applicable"
-
-            val firstExpression = (node.uastBody as? UBlockExpression)
-                    ?.expressions?.firstOrNull()
-
-            if (firstExpression == null) {
-                context.report(
-                    ISSUE_ENFORCE_PERMISSION_HELPER,
-                    context.getLocation(node),
-                    message,
-                )
-                return
-            }
-
-            val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
-              .asSourceString()
-              .filterNot(Char::isWhitespace)
-
-            if (firstExpressionSource != targetExpression &&
-                  firstExpressionSource != "super.$targetExpression") {
-                // calling super.<methodName>() is also legal
-                val directSuper = context.evaluator.getSuperMethod(node)
-                val firstCall = findCallExpression(firstExpression)?.resolve()
-                if (directSuper != null && firstCall == directSuper) return
-
-                val locationTarget = getLocationTarget(firstExpression)
-                val expressionLocation = context.getLocation(locationTarget)
-
-                context.report(
-                    ISSUE_ENFORCE_PERMISSION_HELPER,
-                    context.getLocation(node),
-                    message,
-                    getHelperMethodFix(node, expressionLocation),
-                )
-            }
-        }
-    }
-
-    companion object {
-        private const val HELPER_SUFFIX = "_enforcePermission"
-
-        private const val EXPLANATION = """
-            The @EnforcePermission annotation can only be used on methods whose class extends from
-            the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
-            AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.
-
-            yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
-            either call it directly, or call it indirectly via super.yourMethodName().
-            """
-
-        val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
-                id = "MissingEnforcePermissionHelper",
-                briefDescription = """Missing permission-enforcing method call in AIDL method
-                    |annotated with @EnforcePermission""".trimMargin(),
-                explanation = EXPLANATION,
-                category = Category.SECURITY,
-                priority = 6,
-                severity = Severity.ERROR,
-                implementation = Implementation(
-                        EnforcePermissionHelperDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                )
-        )
-
-        val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
-                id = "MisusingEnforcePermissionAnnotation",
-                briefDescription = "@EnforcePermission cannot be used here",
-                explanation = EXPLANATION,
-                category = Category.SECURITY,
-                priority = 6,
-                severity = Severity.ERROR,
-                implementation = Implementation(
-                        EnforcePermissionDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                )
-        )
-
-        /**
-         * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
-         * resulting in an incorrect Location if used directly
-         */
-        private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
-            if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
-            if (firstExpression is UDeclarationsExpression) {
-                return firstExpression.declarations.firstOrNull()?.sourcePsi
-            }
-            return null
-        }
-    }
-}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 75b0073..2afca05 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -28,7 +28,9 @@
 
     override fun getIssues(): List<Issue> = listOf(
             EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
-            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION
+            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+            EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+            EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
     )
 
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
@@ -41,7 +43,9 @@
             public class TestClass2 extends IFooMethod.Stub {
                 @Override
                 @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-                public void testMethod() {}
+                public void testMethod() {
+                    testMethod_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
@@ -58,7 +62,9 @@
             public class TestClass11 extends IFooMethod.Stub {
                 @Override
                 @EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
-                public void testMethodAll() {}
+                public void testMethodAll() {
+                    testMethodAll_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
@@ -75,7 +81,10 @@
             public class TestClass111 extends IFooMethod.Stub {
                 @Override
                 @EnforcePermission(allOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE})
-                public void testMethodAllLiteral() {}
+                public void testMethodAllLiteral() {
+                    testMethodAllLiteral_enforcePermission();
+
+                }
             }
             """).indented(),
                 *stubs
@@ -92,7 +101,9 @@
             public class TestClass12 extends IFooMethod.Stub {
                 @Override
                 @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
-                public void testMethodAny() {}
+                public void testMethodAny() {
+                    testMethodAny_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
@@ -109,7 +120,9 @@
             public class TestClass121 extends IFooMethod.Stub {
                 @Override
                 @EnforcePermission(anyOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE})
-                public void testMethodAnyLiteral() {}
+                public void testMethodAnyLiteral() {
+                    testMethodAnyLiteral_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
@@ -124,7 +137,9 @@
             package test.pkg;
             public class TestClass4 extends IFooMethod.Stub {
                 @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-                public void testMethod() {}
+                public void testMethod() {
+                    testMethod_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
@@ -132,21 +147,67 @@
         .run()
         .expect("""
                 src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
-                which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
+                which differs from the overridden method IFooMethod.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
                 The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-                    public void testMethod() {}
+                    public void testMethod() {
                                 ~~~~~~~~~~
                 1 errors, 0 warnings
                 """.addLineContinuation())
     }
 
+    fun testDetectIssuesAnnotationOnNonStubMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass42 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
+                public void aRegularMethodNotPartOfStub() {
+                }
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass42.java:3: Error: The method aRegularMethodNotPartOfStub does not override an AIDL generated method [MisusingEnforcePermissionAnnotation]
+                    @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
+                    ^
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectNoIssuesAnnotationOnNonStubMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass43 extends IFooMethod.Stub {
+                public void aRegularMethodNotPartOfStub() {
+                }
+            }
+            """).indented(), java(
+            """
+            package test.pkg;
+            public class TestClass44 extends TestClass43 {
+              @Override
+              public void aRegularMethodNotPartOfStub() {
+              }
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
     fun testDetectIssuesEmptyAnnotationOnMethod() {
         lint().files(java(
             """
             package test.pkg;
             public class TestClass41 extends IFooMethod.Stub {
                 @android.annotation.EnforcePermission
-                public void testMethod() {}
+                public void testMethod() {
+                  testMethod_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
@@ -154,9 +215,9 @@
         .run()
         .expect("""
                 src/test/pkg/TestClass41.java:4: Error: The method TestClass41.testMethod is annotated with @android.annotation.EnforcePermission \
-                which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
+                which differs from the overridden method IFooMethod.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
                 The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-                    public void testMethod() {}
+                    public void testMethod() {
                                 ~~~~~~~~~~
                 1 errors, 0 warnings
                 """.addLineContinuation())
@@ -168,7 +229,9 @@
             package test.pkg;
             public class TestClass9 extends IFooMethod.Stub {
                 @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC})
-                public void testMethodAny() {}
+                public void testMethodAny() {
+                    testMethodAny_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
@@ -177,10 +240,10 @@
         .expect("""
                 src/test/pkg/TestClass9.java:4: Error: The method TestClass9.testMethodAny is annotated with \
                 @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \
-                which differs from the overridden method Stub.testMethodAny: \
+                which differs from the overridden method IFooMethod.testMethodAny: \
                 @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \
                 The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-                    public void testMethodAny() {}
+                    public void testMethodAny() {
                                 ~~~~~~~~~~~~~
                 1 errors, 0 warnings
                 """.addLineContinuation())
@@ -192,7 +255,9 @@
             package test.pkg;
             public class TestClass91 extends IFooMethod.Stub {
                 @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"})
-                public void testMethodAnyLiteral() {}
+                public void testMethodAnyLiteral() {
+                    testMethodAnyLiteral_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
@@ -201,10 +266,10 @@
         .expect("""
                 src/test/pkg/TestClass91.java:4: Error: The method TestClass91.testMethodAnyLiteral is annotated with \
                 @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \
-                which differs from the overridden method Stub.testMethodAnyLiteral: \
+                which differs from the overridden method IFooMethod.testMethodAnyLiteral: \
                 @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
                 The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-                    public void testMethodAnyLiteral() {}
+                    public void testMethodAnyLiteral() {
                                 ~~~~~~~~~~~~~~~~~~~~
                 1 errors, 0 warnings
                 """.addLineContinuation())
@@ -216,7 +281,9 @@
             package test.pkg;
             public class TestClass10 extends IFooMethod.Stub {
                 @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC})
-                public void testMethodAll() {}
+                public void testMethodAll() {
+                    testMethodAll_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
@@ -225,10 +292,10 @@
         .expect("""
                 src/test/pkg/TestClass10.java:4: Error: The method TestClass10.testMethodAll is annotated with \
                 @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \
-                which differs from the overridden method Stub.testMethodAll: \
+                which differs from the overridden method IFooMethod.testMethodAll: \
                 @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \
                 The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-                    public void testMethodAll() {}
+                    public void testMethodAll() {
                                 ~~~~~~~~~~~~~
                 1 errors, 0 warnings
                 """.addLineContinuation())
@@ -240,7 +307,9 @@
             package test.pkg;
             public class TestClass101 extends IFooMethod.Stub {
                 @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"})
-                public void testMethodAllLiteral() {}
+                public void testMethodAllLiteral() {
+                    testMethodAllLiteral_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
@@ -249,10 +318,10 @@
         .expect("""
                 src/test/pkg/TestClass101.java:4: Error: The method TestClass101.testMethodAllLiteral is annotated with \
                 @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \
-                which differs from the overridden method Stub.testMethodAllLiteral: \
+                which differs from the overridden method IFooMethod.testMethodAllLiteral: \
                 @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
                 The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-                    public void testMethodAllLiteral() {}
+                    public void testMethodAllLiteral() {
                                 ~~~~~~~~~~~~~~~~~~~~
                 1 errors, 0 warnings
                 """.addLineContinuation())
@@ -263,16 +332,18 @@
             """
             package test.pkg;
             public class TestClass6 extends IFooMethod.Stub {
-                public void testMethod() {}
+                public void testMethod() {
+                    testMethod_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
         )
         .run()
         .expect("""
-                src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod overrides the method Stub.testMethod which is annotated with @EnforcePermission. \
+                src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod overrides the method IFooMethod.testMethod which is annotated with @EnforcePermission. \
                 The same annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
-                    public void testMethod() {}
+                    public void testMethod() {
                                 ~~~~~~~~~~
                 1 errors, 0 warnings
                 """.addLineContinuation())
@@ -284,16 +355,18 @@
             package test.pkg;
             public class TestClass7 extends IBar.Stub {
                 @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-                public void testMethod() {}
+                public void testMethod() {
+                    testMethod_enforcePermission();
+                }
             }
             """).indented(),
                 *stubs
         )
         .run()
         .expect("""
-                src/test/pkg/TestClass7.java:4: Error: The method TestClass7.testMethod overrides the method Stub.testMethod which is not annotated with @EnforcePermission. \
-                The same annotation must be used on Stub.testMethod. Did you forget to annotate the AIDL definition? [MissingEnforcePermissionAnnotation]
-                    public void testMethod() {}
+                src/test/pkg/TestClass7.java:4: Error: The method TestClass7.testMethod overrides the method IBar.testMethod which is not annotated with @EnforcePermission. \
+                The same annotation must be used on IBar.testMethod. Did you forget to annotate the AIDL definition? [MissingEnforcePermissionAnnotation]
+                    public void testMethod() {
                                 ~~~~~~~~~~
                 1 errors, 0 warnings
                 """.addLineContinuation())
@@ -304,7 +377,9 @@
             """
             package test.pkg;
             public class Default extends IFooMethod.Stub {
-                public void testMethod() {}
+                public void testMethod() {
+                    testMethod_enforcePermission();
+                }
             }
             """).indented(),
             *stubs
@@ -313,23 +388,74 @@
             .expect(
                 """
                 src/test/pkg/Default.java:3: Error: The method Default.testMethod \
-                overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same annotation must be used on Default.testMethod [MissingEnforcePermissionAnnotation]
-                    public void testMethod() {}
+                overrides the method IFooMethod.testMethod which is annotated with @EnforcePermission. The same annotation must be used on Default.testMethod [MissingEnforcePermissionAnnotation]
+                    public void testMethod() {
                                 ~~~~~~~~~~
-                1 errors, 0 warnings 
+                1 errors, 0 warnings
                 """.addLineContinuation()
             )
     }
 
-    fun testDoesDetectIssuesShortStringsNotAllowed() {
+    fun testDoesNotDetectIssuesShortStringsAllowedInChildAndParent() {
         lint().files(java(
             """
             package test.pkg;
             import android.annotation.EnforcePermission;
             public class TestClass121 extends IFooMethod.Stub {
                 @Override
+                @EnforcePermission("READ_PHONE_STATE")
+                public void testMethod() {
+                    testMethod_enforcePermission();
+                }
+                @Override
+                @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                public void testMethodParentShortPermission() {
+                    testMethodParentShortPermission_enforcePermission();
+                }
+                @Override
                 @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
-                public void testMethodAnyLiteral() {}
+                public void testMethodAnyLiteral() {
+                    testMethodAnyLiteral_enforcePermission();
+                }
+                @Override
+                @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAnyLiteralParentsShortPermission() {
+                    testMethodAnyLiteralParentsShortPermission_enforcePermission();
+                }
+            }
+            """).indented(),
+            *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun testDoesDetectIssuesWrongShortStringsInChildAndParent() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass121 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission("READ_WRONG_PHONE_STATE")
+                public void testMethod() {
+                    testMethod_enforcePermission();
+                }
+                @Override
+                @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE)
+                public void testMethodParentShortPermission() {
+                    testMethodParentShortPermission_enforcePermission();
+                }
+                @Override
+                @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"})
+                public void testMethodAnyLiteral() {
+                    testMethodAnyLiteral_enforcePermission();
+                }
+                @Override
+                @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE})
+                public void testMethodAnyLiteralParentsShortPermission() {
+                    testMethodAnyLiteralParentsShortPermission_enforcePermission();
+                }
             }
             """).indented(),
             *stubs
@@ -337,14 +463,19 @@
             .run()
             .expect(
                 """
-                src/test/pkg/TestClass121.java:6: Error: The method \
-                TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \
-                which differs from the overridden method Stub.testMethodAnyLiteral: \
-                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
-                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-                    public void testMethodAnyLiteral() {}
-                                ~~~~~~~~~~~~~~~~~~~~
-                1 errors, 0 warnings
+            src/test/pkg/TestClass121.java:6: Error: The method TestClass121.testMethod is annotated with @EnforcePermission("READ_WRONG_PHONE_STATE") which differs from the overridden method IFooMethod.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                public void testMethod() {
+                            ~~~~~~~~~~
+            src/test/pkg/TestClass121.java:11: Error: The method TestClass121.testMethodParentShortPermission is annotated with @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE) which differs from the overridden method IFooMethod.testMethodParentShortPermission: @android.annotation.EnforcePermission("READ_PHONE_STATE"). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                public void testMethodParentShortPermission() {
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+            src/test/pkg/TestClass121.java:16: Error: The method TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"}) which differs from the overridden method IFooMethod.testMethodAnyLiteral: @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                public void testMethodAnyLiteral() {
+                            ~~~~~~~~~~~~~~~~~~~~
+            src/test/pkg/TestClass121.java:21: Error: The method TestClass121.testMethodAnyLiteralParentsShortPermission is annotated with @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE}) which differs from the overridden method IFooMethod.testMethodAnyLiteralParentsShortPermission: @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                public void testMethodAnyLiteralParentsShortPermission() {
+                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+            4 errors, 0 warnings
                 """.addLineContinuation()
             )
     }
@@ -356,28 +487,17 @@
         """
         public interface IFooMethod extends android.os.IInterface {
          public static abstract class Stub extends android.os.Binder implements IFooMethod {
-            @Override
-            @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-            public void testMethod() {}
-            @Override
-            @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
-            public void testMethodAny() {}
-            @Override
-            @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
-            public void testMethodAnyLiteral() {}
-            @Override
-            @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
-            public void testMethodAll() {}
-            @Override
-            @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
-            public void testMethodAllLiteral() {}
           }
           @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
           public void testMethod();
+          @android.annotation.EnforcePermission("READ_PHONE_STATE")
+          public void testMethodParentShortPermission();
           @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
           public void testMethodAny() {}
           @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
           public void testMethodAnyLiteral() {}
+          @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+          public void testMethodAnyLiteralParentsShortPermission() {}
           @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
           public void testMethodAll() {}
           @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
@@ -391,8 +511,6 @@
         """
         public interface IBar extends android.os.IInterface {
          public static abstract class Stub extends android.os.Binder implements IBar {
-            @Override
-            public void testMethod() {}
           }
           public void testMethod();
         }
@@ -404,6 +522,7 @@
         package android.Manifest;
         class permission {
           public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+          public static final String READ_WRONG_PHONE_STATE = "android.permission.READ_WRONG_PHONE_STATE";
           public static final String NFC = "android.permission.NFC";
           public static final String INTERNET = "android.permission.INTERNET";
         }
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
index 5a63bb4..a4b0bc3 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
@@ -25,10 +25,11 @@
 
 @Suppress("UnstableApiUsage")
 class EnforcePermissionHelperDetectorCodegenTest : LintDetectorTest() {
-    override fun getDetector(): Detector = EnforcePermissionHelperDetector()
+    override fun getDetector(): Detector = EnforcePermissionDetector()
 
     override fun getIssues(): List<Issue> = listOf(
-            EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER
+            EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+            EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
     )
 
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
index 10a6e1d..64e2bfb 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -20,10 +20,10 @@
 import com.android.tools.lint.checks.infrastructure.TestLintTask
 
 class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
-    override fun getDetector() = EnforcePermissionHelperDetector()
+    override fun getDetector() = EnforcePermissionDetector()
     override fun getIssues() = listOf(
-        EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
-        EnforcePermissionHelperDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
+        EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+        EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
     )
 
     override fun lint(): TestLintTask = super.lint().allowMissingSdk()