Merge "Remove flexiglass dependency from repository that is used by existing code" into main
diff --git a/Android.bp b/Android.bp
index 7d5e788..986a071 100644
--- a/Android.bp
+++ b/Android.bp
@@ -614,30 +614,6 @@
     ],
 }
 
-// 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 " +
-    "--error UnflaggedApi " +
-    "--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",
@@ -706,6 +682,29 @@
         "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 HiddenTypeParameter",
+        "--hide MissingPermission",
+        "--hide RequiresPermission",
+        "--hide SdkConstant",
+        "--hide Todo",
+        "--hide UnavailableSymbol",
+        "--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,
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 3e835b8..eb5502b 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -3,6 +3,6 @@
 flag {
     name: "relax_prefetch_connectivity_constraint_only_on_charger"
     namespace: "backstage_power"
-    description: "Only relax a prefetch job's connectivity constraint when the device is charging"
+    description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low"
     bug: "299329948"
 }
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 5bf2eb9..6550f26 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1518,8 +1518,8 @@
             @WorkType final int workType) {
         final List<StateController> controllers = mService.mControllers;
         final int numControllers = controllers.size();
-        final PowerManager.WakeLock wl =
-                mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, jobStatus.getTag());
+        final PowerManager.WakeLock wl = mPowerManager.newWakeLock(
+                PowerManager.PARTIAL_WAKE_LOCK, jobStatus.getWakelockTag());
         wl.setWorkSource(mService.deriveWorkSource(
                 jobStatus.getSourceUid(), jobStatus.getSourcePackageName()));
         wl.setReferenceCounted(false);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index bbe1485..592aff8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -5512,7 +5512,6 @@
                 pw.print("Evaluated bias: ");
                 pw.println(JobInfo.getBiasString(bias));
 
-                pw.print("Tag: "); pw.println(job.getTag());
                 pw.print("Enq: ");
                 TimeUtils.formatDuration(job.madePending - nowUptime, pw);
                 pw.decreaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 4357d4f..293088d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -180,7 +180,7 @@
 
                 case "-u":
                 case "--user":
-                    userId = Integer.parseInt(getNextArgRequired());
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
                     break;
 
                 case "-n":
@@ -199,6 +199,10 @@
             return -1;
         }
 
+        if (userId == UserHandle.USER_CURRENT) {
+            userId = ActivityManager.getCurrentUser();
+        }
+
         final String pkgName = getNextArgRequired();
         final int jobId = Integer.parseInt(getNextArgRequired());
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index f47766e..79653f0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -398,7 +398,8 @@
             // it was inflated from disk with not-yet-coherent delay/deadline bounds.
             job.clearPersistedUtcTimes();
 
-            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, job.getTag());
+            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                    job.getWakelockTag());
             mWakeLock.setWorkSource(
                     mService.deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
             mWakeLock.setReferenceCounted(false);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 45f15db..63eaa63 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -932,7 +932,9 @@
             return false;
         }
         if (relaxPrefetchConnectivityConstraintOnlyOnCharger()) {
-            if (!mService.isBatteryCharging()) {
+            // Since the constraint relaxation isn't required by the job, only do it when the
+            // device is charging and the battery level is above the "low battery" threshold.
+            if (!mService.isBatteryCharging() || !mService.isBatteryNotLow()) {
                 return false;
             }
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 458ff35..1fb54d5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -253,7 +253,12 @@
     /** An ID that can be used to uniquely identify the job when logging statsd metrics. */
     private final long mLoggingJobId;
 
-    final String tag;
+    /**
+     * Tag to identify the wakelock held for this job. Lazily loaded in
+     * {@link #getWakelockTag()} since it's not typically needed until the job is about to run.
+     */
+    @Nullable
+    private String mWakelockTag;
 
     /** Whether this job was scheduled by one app on behalf of another. */
     final boolean mIsProxyJob;
@@ -627,7 +632,6 @@
         this.batteryName = this.sourceTag != null
                 ? bnNamespace + this.sourceTag + ":" + job.getService().getPackageName()
                 : bnNamespace + job.getService().flattenToShortString();
-        this.tag = "*job*/" + this.batteryName + "#" + job.getId();
 
         final String componentPackage = job.getService().getPackageName();
         mIsProxyJob = !this.sourcePackageName.equals(componentPackage);
@@ -1321,8 +1325,13 @@
         return batteryName;
     }
 
-    public String getTag() {
-        return tag;
+    /** Return the String to be used as the tag for the wakelock held for this job. */
+    @NonNull
+    public String getWakelockTag() {
+        if (mWakelockTag == null) {
+            mWakelockTag = "*job*/" + this.batteryName;
+        }
+        return mWakelockTag;
     }
 
     public int getBias() {
@@ -2639,7 +2648,7 @@
     @NeverCompile // Avoid size overhead of debugging code.
     public void dump(IndentingPrintWriter pw,  boolean full, long nowElapsed) {
         UserHandle.formatUid(pw, callingUid);
-        pw.print(" tag="); pw.println(tag);
+        pw.print(" tag="); pw.println(getWakelockTag());
 
         pw.print("Source: uid="); UserHandle.formatUid(pw, getSourceUid());
         pw.print(" user="); pw.print(getSourceUserId());
@@ -2955,7 +2964,7 @@
         final long token = proto.start(fieldId);
 
         proto.write(JobStatusDumpProto.CALLING_UID, callingUid);
-        proto.write(JobStatusDumpProto.TAG, tag);
+        proto.write(JobStatusDumpProto.TAG, getWakelockTag());
         proto.write(JobStatusDumpProto.SOURCE_UID, getSourceUid());
         proto.write(JobStatusDumpProto.SOURCE_USER_ID, getSourceUserId());
         proto.write(JobStatusDumpProto.SOURCE_PACKAGE_NAME, getSourcePackageName());
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index e086bfe..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: [
@@ -129,8 +128,7 @@
 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\\) ",
+    flags: ["--show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)"],
     api_levels_module: "api_versions_system",
 }
 
@@ -139,30 +137,6 @@
 // using droiddoc
 /////////////////////////////////////////////////////////////////////
 
-// doclava contains checks for a few issues that are have been migrated to metalava.
-// disable them in doclava, to avoid mistriggering or double triggering.
-ignore_doclava_errors_checked_by_metalava = "" +
-    "-hide 111 " + // HIDDEN_SUPERCLASS
-    "-hide 113 " + // DEPRECATION_MISMATCH
-    "-hide 125 " + // REQUIRES_PERMISSION
-    "-hide 126 " + // BROADCAST_BEHAVIOR
-    "-hide 127 " + // SDK_CONSTANT
-    "-hide 128 " // TODO
-
-framework_docs_only_args = "-android " +
-    "-manifest $(location :frameworks-base-core-AndroidManifest.xml) " +
-    "-metalavaApiSince " +
-    "-werror " +
-    "-lerror " +
-    ignore_doclava_errors_checked_by_metalava +
-    "-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",
@@ -182,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",
@@ -217,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",
 }
 
@@ -234,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",
 }
@@ -252,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",
 }
@@ -269,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 {
@@ -292,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,
 }
@@ -346,11 +364,12 @@
     hdf: [
         "android.whichdoc online",
     ],
-    args: framework_docs_only_args +
-        " -staticonly " +
-        " -toroot / " +
-        " -devsite " +
-        " -ignoreJdLinks ",
+    flags: [
+        "-staticonly",
+        "-toroot /",
+        "-devsite",
+        "-ignoreJdLinks",
+    ],
 }
 
 droiddoc {
@@ -362,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 7e78185..d566552 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -36,7 +36,6 @@
         "android-non-updatable-stubs-defaults",
         "module-classpath-stubs-defaults",
     ],
-    args: metalava_framework_docs_args,
     check_api: {
         current: {
             api_file: ":non-updatable-current.txt",
@@ -70,19 +69,25 @@
     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",
@@ -93,7 +98,7 @@
         "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",
@@ -136,7 +141,7 @@
         "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",
@@ -186,7 +191,7 @@
         "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",
@@ -972,7 +977,7 @@
     merge_annotations_dirs: [
         "metalava-manual",
     ],
-    args: priv_apps,
+    flags: priv_apps,
 }
 
 java_library {
diff --git a/core/api/current.txt b/core/api/current.txt
index 28f83d8..177352f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -232,6 +232,7 @@
     field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
     field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
     field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
+    field @FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission") public static final String READ_DROPBOX_DATA = "android.permission.READ_DROPBOX_DATA";
     field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
     field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA";
     field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
@@ -12888,7 +12889,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";
@@ -17712,12 +17713,14 @@
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_DISABLED = 0; // 0x0
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_ENABLED = 1; // 0x1
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_UNSPECIFIED = -1; // 0xffffffff
+    field @FlaggedApi("com.android.text.flags.word_style_auto") public static final int LINE_BREAK_STYLE_AUTO = 5; // 0x5
     field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
     field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_NO_BREAK = 4; // 0x4
     field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; // 0xffffffff
+    field @FlaggedApi("com.android.text.flags.word_style_auto") public static final int LINE_BREAK_WORD_STYLE_AUTO = 2; // 0x2
     field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; // 0xffffffff
@@ -32440,7 +32443,7 @@
     method public void addData(@NonNull String, @Nullable byte[], int);
     method public void addFile(@NonNull String, @NonNull java.io.File, int) throws java.io.IOException;
     method public void addText(@NonNull String, @NonNull String);
-    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_LOGS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long);
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_DROPBOX_DATA, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long);
     method public boolean isTagEnabled(String);
     field public static final String ACTION_DROPBOX_ENTRY_ADDED = "android.intent.action.DROPBOX_ENTRY_ADDED";
     field public static final String EXTRA_DROPPED_COUNT = "android.os.extra.DROPPED_COUNT";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 836a016..8ea1c65 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3487,7 +3487,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";
@@ -5584,7 +5584,8 @@
     method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
     method public void close();
-    method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+    method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier);
     method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback);
     method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback);
     method public void removeOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
@@ -5638,12 +5639,13 @@
     field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
     field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
     field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf
     field public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004; // 0x2714
     field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
     field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
     field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
-    field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
-    field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
+    field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
+    field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
     field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf
     field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf
     field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8
@@ -5658,6 +5660,14 @@
     field @Deprecated public static final int PROGRAM_TYPE_SXM = 7; // 0x7
     field @Deprecated public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf
     field @Deprecated public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_1 = 1; // 0x1
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_2 = 2; // 0x2
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_3 = 4; // 0x4
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_4 = 8; // 0x8
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_5 = 16; // 0x10
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_6 = 32; // 0x20
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_7 = 64; // 0x40
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int SUB_CHANNEL_HD_8 = 128; // 0x80
   }
 
   public static final class ProgramSelector.Identifier implements android.os.Parcelable {
@@ -5670,7 +5680,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR;
   }
 
-  @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
+  @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_LOCATION}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
   }
 
   @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType {
@@ -5694,7 +5704,9 @@
     field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
     field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
     field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
-    field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+    field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa
     field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3
     field public static final int CONFIG_FORCE_MONO = 1; // 0x1
     field public static final int CONFIG_RDS_AF = 4; // 0x4
@@ -5822,8 +5834,11 @@
     method @Deprecated public int getSubChannel();
     method @NonNull public java.util.Map<java.lang.String,java.lang.String> getVendorInfo();
     method @Deprecated public boolean isDigital();
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isHdAudioAvailable();
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isHdSisAvailable();
     method public boolean isLive();
     method public boolean isMuted();
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") public boolean isSignalAcquired();
     method public boolean isStereo();
     method public boolean isTrafficAnnouncementActive();
     method public boolean isTrafficProgram();
@@ -5839,6 +5854,7 @@
     method public android.hardware.radio.RadioMetadata.Clock getClock(String);
     method public int getInt(String);
     method public String getString(String);
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public String[] getStringArray(@NonNull String);
     method public java.util.Set<java.lang.String> keySet();
     method public int size();
     method public void writeToParcel(android.os.Parcel, int);
@@ -5847,6 +5863,9 @@
     field public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART";
     field public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST";
     field public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMENT_ACTUAL_TEXT = "android.hardware.radio.metadata.COMMENT_ACTUAL_TEXT";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMENT_SHORT_DESCRIPTION = "android.hardware.radio.metadata.COMMENT_SHORT_DESCRIPTION";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_COMMERCIAL = "android.hardware.radio.metadata.COMMERCIAL";
     field public static final String METADATA_KEY_DAB_COMPONENT_NAME = "android.hardware.radio.metadata.DAB_COMPONENT_NAME";
     field public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT = "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT";
     field public static final String METADATA_KEY_DAB_ENSEMBLE_NAME = "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME";
@@ -5854,6 +5873,9 @@
     field public static final String METADATA_KEY_DAB_SERVICE_NAME = "android.hardware.radio.metadata.DAB_SERVICE_NAME";
     field public static final String METADATA_KEY_DAB_SERVICE_NAME_SHORT = "android.hardware.radio.metadata.DAB_SERVICE_NAME_SHORT";
     field public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_STATION_NAME_LONG = "android.hardware.radio.metadata.HD_STATION_NAME_LONG";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_STATION_NAME_SHORT = "android.hardware.radio.metadata.HD_STATION_NAME_SHORT";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_HD_SUBCHANNELS_AVAILABLE = "android.hardware.radio.metadata.HD_SUBCHANNELS_AVAILABLE";
     field public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON";
     field public static final String METADATA_KEY_PROGRAM_NAME = "android.hardware.radio.metadata.PROGRAM_NAME";
     field public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY";
@@ -5862,6 +5884,7 @@
     field public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY";
     field public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT";
     field public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE";
+    field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final String METADATA_KEY_UFIDS = "android.hardware.radio.metadata.UFIDS";
   }
 
   public static final class RadioMetadata.Builder {
@@ -5872,6 +5895,7 @@
     method public android.hardware.radio.RadioMetadata.Builder putClock(String, long, int);
     method public android.hardware.radio.RadioMetadata.Builder putInt(String, int);
     method public android.hardware.radio.RadioMetadata.Builder putString(String, String);
+    method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public android.hardware.radio.RadioMetadata.Builder putStringArray(@NonNull String, @NonNull String[]);
   }
 
   public static final class RadioMetadata.Clock implements android.os.Parcelable {
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 37111e9..c8317c8 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -27,7 +27,15 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.WireTypeMismatchException;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -608,6 +616,103 @@
                 }
             };
 
+    /**
+     * Write to a protocol buffer output stream. Protocol buffer message definition at {@link
+     * android.app.ApplicationStartInfoProto}
+     *
+     * @param proto Stream to write the ApplicationStartInfo object to.
+     * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message
+     * @hide
+     */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException {
+        final long token = proto.start(fieldId);
+        proto.write(ApplicationStartInfoProto.PID, mPid);
+        proto.write(ApplicationStartInfoProto.REAL_UID, mRealUid);
+        proto.write(ApplicationStartInfoProto.PACKAGE_UID, mPackageUid);
+        proto.write(ApplicationStartInfoProto.DEFINING_UID, mDefiningUid);
+        proto.write(ApplicationStartInfoProto.PROCESS_NAME, mProcessName);
+        proto.write(ApplicationStartInfoProto.STARTUP_STATE, mStartupState);
+        proto.write(ApplicationStartInfoProto.REASON, mReason);
+        if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) {
+            ByteArrayOutputStream timestampsBytes = new ByteArrayOutputStream();
+            ObjectOutputStream timestampsOut = new ObjectOutputStream(timestampsBytes);
+            timestampsOut.writeObject(mStartupTimestampsNs);
+            proto.write(ApplicationStartInfoProto.STARTUP_TIMESTAMPS,
+                    timestampsBytes.toByteArray());
+        }
+        proto.write(ApplicationStartInfoProto.START_TYPE, mStartType);
+        if (mStartIntent != null) {
+            Parcel parcel = Parcel.obtain();
+            mStartIntent.writeToParcel(parcel, 0);
+            proto.write(ApplicationStartInfoProto.START_INTENT, parcel.marshall());
+            parcel.recycle();
+        }
+        proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
+        proto.end(token);
+    }
+
+    /**
+     * Read from a protocol buffer input stream. Protocol buffer message definition at {@link
+     * android.app.ApplicationStartInfoProto}
+     *
+     * @param proto Stream to read the ApplicationStartInfo object from.
+     * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message
+     * @hide
+     */
+    public void readFromProto(ProtoInputStream proto, long fieldId)
+            throws IOException, WireTypeMismatchException, ClassNotFoundException {
+        final long token = proto.start(fieldId);
+        while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (proto.getFieldNumber()) {
+                case (int) ApplicationStartInfoProto.PID:
+                    mPid = proto.readInt(ApplicationStartInfoProto.PID);
+                    break;
+                case (int) ApplicationStartInfoProto.REAL_UID:
+                    mRealUid = proto.readInt(ApplicationStartInfoProto.REAL_UID);
+                    break;
+                case (int) ApplicationStartInfoProto.PACKAGE_UID:
+                    mPackageUid = proto.readInt(ApplicationStartInfoProto.PACKAGE_UID);
+                    break;
+                case (int) ApplicationStartInfoProto.DEFINING_UID:
+                    mDefiningUid = proto.readInt(ApplicationStartInfoProto.DEFINING_UID);
+                    break;
+                case (int) ApplicationStartInfoProto.PROCESS_NAME:
+                    mProcessName = intern(proto.readString(ApplicationStartInfoProto.PROCESS_NAME));
+                    break;
+                case (int) ApplicationStartInfoProto.STARTUP_STATE:
+                    mStartupState = proto.readInt(ApplicationStartInfoProto.STARTUP_STATE);
+                    break;
+                case (int) ApplicationStartInfoProto.REASON:
+                    mReason = proto.readInt(ApplicationStartInfoProto.REASON);
+                    break;
+                case (int) ApplicationStartInfoProto.STARTUP_TIMESTAMPS:
+                    ByteArrayInputStream timestampsBytes = new ByteArrayInputStream(proto.readBytes(
+                            ApplicationStartInfoProto.STARTUP_TIMESTAMPS));
+                    ObjectInputStream timestampsIn = new ObjectInputStream(timestampsBytes);
+                    mStartupTimestampsNs = (ArrayMap<Integer, Long>) timestampsIn.readObject();
+                    break;
+                case (int) ApplicationStartInfoProto.START_TYPE:
+                    mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE);
+                    break;
+                case (int) ApplicationStartInfoProto.START_INTENT:
+                    byte[] startIntentBytes = proto.readBytes(
+                        ApplicationStartInfoProto.START_INTENT);
+                    if (startIntentBytes.length > 0) {
+                        Parcel parcel = Parcel.obtain();
+                        parcel.unmarshall(startIntentBytes, 0, startIntentBytes.length);
+                        parcel.setDataPosition(0);
+                        mStartIntent = Intent.CREATOR.createFromParcel(parcel);
+                        parcel.recycle();
+                    }
+                    break;
+                case (int) ApplicationStartInfoProto.LAUNCH_MODE:
+                    mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE);
+                    break;
+            }
+        }
+        proto.end(token);
+    }
+
     /** @hide */
     public void dump(@NonNull PrintWriter pw, @Nullable String prefix, @Nullable String seqSuffix,
             @NonNull SimpleDateFormat sdf) {
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.aidl b/core/java/android/app/usage/ParcelableUsageEventList.aidl
new file mode 100644
index 0000000..1652996
--- /dev/null
+++ b/core/java/android/app/usage/ParcelableUsageEventList.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.app.usage;
+
+parcelable ParcelableUsageEventList;
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java
new file mode 100644
index 0000000..016d97f
--- /dev/null
+++ b/core/java/android/app/usage/ParcelableUsageEventList.java
@@ -0,0 +1,266 @@
+/*
+ * 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.app.usage;
+
+import android.annotation.NonNull;
+import android.app.usage.UsageEvents.Event;
+import android.content.res.Configuration;
+import android.os.BadParcelableException;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a copied version of BaseParceledListSlice with specific
+ * {@link UsageEvents.Event} instance that used to transfer the large
+ * list of {@link UsageEvents.Event} objects across an IPC. Splits
+ * into multiple transactions if needed.
+ *
+ * @see BasedParceledListSlice
+ *
+ * @hide
+ */
+public final class ParcelableUsageEventList implements Parcelable {
+    private static final String TAG = "ParcelableUsageEventList";
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_ALL = false;
+
+    private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
+
+    private List<Event> mList;
+
+    public ParcelableUsageEventList(List<Event> list) {
+        mList = list;
+    }
+
+    private ParcelableUsageEventList(Parcel in) {
+        final int N = in.readInt();
+        mList = new ArrayList<>();
+        if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
+        if (N <= 0) {
+            return;
+        }
+
+        int i = 0;
+        while (i < N) {
+            if (in.readInt() == 0) {
+                break;
+            }
+            mList.add(readEventFromParcel(in));
+            if (DEBUG_ALL) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size() - 1));
+            i++;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Read " + mList.size() + " inline UsageEvents"
+                    + ", total N=" + N + " UsageEvents");
+        }
+        if (i >= N) {
+            return;
+        }
+        final IBinder retriever = in.readStrongBinder();
+        while (i < N) {
+            if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            data.writeInt(i);
+            try {
+                retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+                reply.readException();
+                int count = 0;
+                while (i < N && reply.readInt() != 0) {
+                    mList.add(readEventFromParcel(reply));
+                    if (DEBUG_ALL) {
+                        Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size() - 1));
+                    }
+                    i++;
+                    count++;
+                }
+                if (DEBUG) Log.d(TAG, "Read extra @" + count + " of " + N);
+            } catch (RemoteException e) {
+                throw new BadParcelableException(
+                    "Failure retrieving array; only received " + i + " of " + N, e);
+            } finally {
+                reply.recycle();
+                data.recycle();
+            }
+        }
+        if (DEBUG) Log.d(TAG, "Finish reading total " + i + " UsageEvents");
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        final int N = mList.size();
+        final int callFlags = flags;
+        dest.writeInt(N);
+        if (DEBUG) Log.d(TAG, "Writing " + N + " items");
+        if (N > 0) {
+            int i = 0;
+            while (i < N && dest.dataSize() < MAX_IPC_SIZE) {
+                dest.writeInt(1);
+
+                final Event event = mList.get(i);
+                writeEventToParcel(event, dest, callFlags);
+
+                if (DEBUG_ALL) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+                i++;
+            }
+            if (i < N) {
+                dest.writeInt(0);
+                Binder retriever = new Binder() {
+                    @Override
+                    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                            throws RemoteException {
+                        if (code != FIRST_CALL_TRANSACTION) {
+                            return super.onTransact(code, data, reply, flags);
+                        } else if (mList == null) {
+                            throw new IllegalArgumentException("Attempt to transfer null list, "
+                                + "did transfer finish?");
+                        }
+                        int i = data.readInt();
+
+                        if (DEBUG) {
+                            Log.d(TAG, "Writing more @" + i + " of " + N + " to "
+                                    + Binder.getCallingPid() + ", sender=" + this);
+                        }
+
+                        try {
+                            reply.writeNoException();
+                            int count = 0;
+                            while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
+                                reply.writeInt(1);
+
+                                final Event event = mList.get(i);
+                                writeEventToParcel(event, reply, callFlags);
+
+                                if (DEBUG_ALL) {
+                                    Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+                                }
+                                i++;
+                                count++;
+                            }
+                            if (i < N) {
+                                if (DEBUG) {
+                                    Log.d(TAG, "Breaking @" + i + " of " + N
+                                            + "(count = " + count + ")");
+                                }
+                                reply.writeInt(0);
+                            } else {
+                                if (DEBUG) Log.d(TAG, "Transfer done, clearing mList reference");
+                                mList = null;
+                            }
+                        } catch (RuntimeException e) {
+                            if (DEBUG) Log.d(TAG, "Transfer failed, clearing mList reference");
+                            mList = null;
+                            throw e;
+                        }
+                        return true;
+                    }
+                };
+                if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
+                dest.writeStrongBinder(retriever);
+            }
+        }
+    }
+
+    public List<Event> getList() {
+        return mList;
+    }
+
+    public static final Parcelable.Creator<ParcelableUsageEventList> CREATOR =
+            new Parcelable.Creator<ParcelableUsageEventList>() {
+                public ParcelableUsageEventList createFromParcel(Parcel in) {
+                    return new ParcelableUsageEventList(in);
+                }
+
+                @Override
+                public ParcelableUsageEventList[] newArray(int size) {
+                    return new ParcelableUsageEventList[size];
+                }
+            };
+
+    private Event readEventFromParcel(Parcel in) {
+        final Event event = new Event();
+        event.mPackage = in.readString();
+        event.mClass = in.readString();
+        event.mInstanceId = in.readInt();
+        event.mTaskRootPackage = in.readString();
+        event.mTaskRootClass = in.readString();
+        event.mEventType = in.readInt();
+        event.mTimeStamp = in.readLong();
+
+        // Fill out the event-dependant fields.
+        event.mConfiguration = null;
+        event.mShortcutId = null;
+        event.mAction = null;
+        event.mContentType = null;
+        event.mContentAnnotations = null;
+        event.mNotificationChannelId = null;
+        event.mLocusId = null;
+
+        switch (event.mEventType) {
+            case Event.CONFIGURATION_CHANGE -> {
+                event.mConfiguration = Configuration.CREATOR.createFromParcel(in);
+            }
+            case Event.SHORTCUT_INVOCATION -> event.mShortcutId = in.readString();
+            case Event.CHOOSER_ACTION -> {
+                event.mAction = in.readString();
+                event.mContentType = in.readString();
+                event.mContentAnnotations = in.readStringArray();
+            }
+            case Event.STANDBY_BUCKET_CHANGED -> event.mBucketAndReason = in.readInt();
+            case Event.NOTIFICATION_INTERRUPTION -> event.mNotificationChannelId = in.readString();
+            case Event.LOCUS_ID_SET -> event.mLocusId = in.readString();
+        }
+        event.mFlags = in.readInt();
+
+        return event;
+    }
+
+    private void writeEventToParcel(@NonNull Event event, @NonNull Parcel dest, int flags) {
+        dest.writeString(event.mPackage);
+        dest.writeString(event.mClass);
+        dest.writeInt(event.mInstanceId);
+        dest.writeString(event.mTaskRootPackage);
+        dest.writeString(event.mTaskRootClass);
+        dest.writeInt(event.mEventType);
+        dest.writeLong(event.mTimeStamp);
+
+        switch (event.mEventType) {
+            case Event.CONFIGURATION_CHANGE -> event.mConfiguration.writeToParcel(dest, flags);
+            case Event.SHORTCUT_INVOCATION -> dest.writeString(event.mShortcutId);
+            case Event.CHOOSER_ACTION -> {
+                dest.writeString(event.mAction);
+                dest.writeString(event.mContentType);
+                dest.writeStringArray(event.mContentAnnotations);
+            }
+            case Event.STANDBY_BUCKET_CHANGED -> dest.writeInt(event.mBucketAndReason);
+            case Event.NOTIFICATION_INTERRUPTION -> dest.writeString(event.mNotificationChannelId);
+            case Event.LOCUS_ID_SET -> dest.writeString(event.mLocusId);
+        }
+        dest.writeInt(event.mFlags);
+    }
+}
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 3c256ad..c188686 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -714,7 +714,7 @@
     @UnsupportedAppUsage
     private Parcel mParcel = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private final int mEventCount;
+    private int mEventCount;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private int mIndex = 0;
@@ -735,6 +735,23 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public UsageEvents(Parcel in) {
+        if (Flags.useParceledList()) {
+            readUsageEventsFromParcelWithParceledList(in);
+        } else {
+            readUsageEventsFromParcelWithBlob(in);
+        }
+
+        mIncludeTaskRoots = true;
+    }
+
+    private void readUsageEventsFromParcelWithParceledList(Parcel in) {
+        mIndex = in.readInt();
+        mEventsToWrite = in.readParcelable(UsageEvents.class.getClassLoader(),
+            ParcelableUsageEventList.class).getList();
+        mEventCount = mEventsToWrite.size();
+    }
+
+    private void readUsageEventsFromParcelWithBlob(Parcel in) {
         byte[] bytes = in.readBlob();
         Parcel data = Parcel.obtain();
         data.unmarshall(bytes, 0, bytes.length);
@@ -752,7 +769,6 @@
             mParcel.setDataSize(mParcel.dataPosition());
             mParcel.setDataPosition(positionInParcel);
         }
-        mIncludeTaskRoots = true;
     }
 
     /**
@@ -810,6 +826,10 @@
             return false;
         }
 
+        if (Flags.useParceledList()) {
+            return getNextEventFromParceledList(eventOut);
+        }
+
         if (mParcel != null) {
             readEventFromParcel(mParcel, eventOut);
         } else {
@@ -824,6 +844,12 @@
         return true;
     }
 
+    private boolean getNextEventFromParceledList(Event eventOut) {
+        eventOut.copyFrom(mEventsToWrite.get(mIndex));
+        mIndex++;
+        return true;
+    }
+
     /**
      * Resets the collection so that it can be iterated over from the beginning.
      *
@@ -968,7 +994,7 @@
             case Event.CHOOSER_ACTION:
                 eventOut.mAction = p.readString();
                 eventOut.mContentType = p.readString();
-                eventOut.mContentAnnotations = p.createStringArray();
+                eventOut.mContentAnnotations = p.readStringArray();
                 break;
             case Event.STANDBY_BUCKET_CHANGED:
                 eventOut.mBucketAndReason = p.readInt();
@@ -990,6 +1016,19 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        if (Flags.useParceledList()) {
+            writeUsageEventsToParcelWithParceledList(dest, flags);
+        } else {
+            writeUsageEventsToParcelWithBlob(dest, flags);
+        }
+    }
+
+    private void writeUsageEventsToParcelWithParceledList(Parcel dest, int flags) {
+        dest.writeInt(mIndex);
+        dest.writeParcelable(new ParcelableUsageEventList(mEventsToWrite), flags);
+    }
+
+    private void writeUsageEventsToParcelWithBlob(Parcel dest, int flags) {
         Parcel data = Parcel.obtain();
         data.writeInt(mEventCount);
         data.writeInt(mIndex);
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index 4f1c65b..0b8e29f 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -21,3 +21,10 @@
     is_fixed_read_only: true
     bug: "299336442"
 }
+
+flag {
+    name: "use_parceled_list"
+    namespace: "backstage_power"
+    description: "Flag for parcelable usage event list"
+    bug: "301254110"
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7afc29c..75370d9 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;
@@ -4773,6 +4774,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/pm/ArchivedActivity.java b/core/java/android/content/pm/ArchivedActivity.java
index 5139e2d..9e49c9e 100644
--- a/core/java/android/content/pm/ArchivedActivity.java
+++ b/core/java/android/content/pm/ArchivedActivity.java
@@ -79,14 +79,14 @@
      * @hide
      */
     public static Bitmap drawableToBitmap(Drawable drawable) {
-        return drawableToBitmap(drawable, /* maxIconSize= */ Integer.MAX_VALUE);
+        return drawableToBitmap(drawable, /* iconSize= */ 0);
     }
 
     /**
-     * Same as above, but.
+     * Same as above, but scale the resulting image to fit iconSize.
      * @hide
      */
-    public static Bitmap drawableToBitmap(Drawable drawable, int maxIconSize) {
+    public static Bitmap drawableToBitmap(Drawable drawable, int iconSize) {
         if (drawable instanceof BitmapDrawable) {
             return ((BitmapDrawable) drawable).getBitmap();
 
@@ -106,8 +106,8 @@
         Canvas canvas = new Canvas(bitmap);
         drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
         drawable.draw(canvas);
-        if (bitmap.getWidth() > maxIconSize || bitmap.getHeight() > maxIconSize) {
-            var scaledBitmap = Bitmap.createScaledBitmap(bitmap, maxIconSize, maxIconSize, true);
+        if (iconSize > 0 && bitmap.getWidth() > iconSize * 2 || bitmap.getHeight() > iconSize * 2) {
+            var scaledBitmap = Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, true);
             if (scaledBitmap != bitmap) {
                 bitmap.recycle();
             }
@@ -118,7 +118,6 @@
 
     /**
      * Compress bitmap to PNG format.
-     * The bitmap is going to be recycled.
      * @hide
      */
     public static byte[] bytesFromBitmap(Bitmap bitmap) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6d4276d..ad7dd51 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3778,6 +3778,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/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index 4f07acf..c5167db 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -17,6 +17,7 @@
 package android.hardware.radio;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -45,7 +46,7 @@
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
-    private final Map<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
+    private final ArrayMap<ProgramSelector.Identifier, ArrayMap<UniqueProgramIdentifier,
             RadioManager.ProgramInfo>> mPrograms = new ArrayMap<>();
 
     @GuardedBy("mLock")
@@ -203,11 +204,11 @@
             listCallbacksCopied = new ArrayList<>(mListCallbacks);
 
             if (chunk.isPurge()) {
-                Iterator<Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
-                        RadioManager.ProgramInfo>>> programsIterator =
-                        mPrograms.entrySet().iterator();
+                Iterator<Map.Entry<ProgramSelector.Identifier,
+                        ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo>>>
+                        programsIterator = mPrograms.entrySet().iterator();
                 while (programsIterator.hasNext()) {
-                    Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
+                    Map.Entry<ProgramSelector.Identifier, ArrayMap<UniqueProgramIdentifier,
                             RadioManager.ProgramInfo>> removed = programsIterator.next();
                     if (removed.getValue() != null) {
                         removedList.add(removed.getKey());
@@ -270,8 +271,7 @@
         if (!mPrograms.containsKey(primaryKey)) {
             return;
         }
-        Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = mPrograms
-                .get(primaryKey);
+        Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries = mPrograms.get(primaryKey);
         RadioManager.ProgramInfo removed = entries.remove(Objects.requireNonNull(key));
         if (removed == null) return;
         if (entries.size() == 0) {
@@ -287,15 +287,10 @@
     public @NonNull List<RadioManager.ProgramInfo> toList() {
         List<RadioManager.ProgramInfo> list = new ArrayList<>();
         synchronized (mLock) {
-            Iterator<Map.Entry<ProgramSelector.Identifier, Map<UniqueProgramIdentifier,
-                    RadioManager.ProgramInfo>>> listIterator = mPrograms.entrySet().iterator();
-            while (listIterator.hasNext()) {
-                Iterator<Map.Entry<UniqueProgramIdentifier,
-                        RadioManager.ProgramInfo>> prorgramsIterator = listIterator.next()
-                        .getValue().entrySet().iterator();
-                while (prorgramsIterator.hasNext()) {
-                    list.add(prorgramsIterator.next().getValue());
-                }
+            for (int index = 0; index < mPrograms.size(); index++) {
+                ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries =
+                        mPrograms.valueAt(index);
+                list.addAll(entries.values());
             }
         }
         return list;
@@ -304,9 +299,16 @@
     /**
      * Returns the program with a specified primary identifier.
      *
+     * <p>This method only returns the first program from the list return from
+     * {@link #getProgramInfos}
+     *
      * @param id primary identifier of a program to fetch
      * @return the program info, or null if there is no such program on the list
+     *
+     * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs
+     * with the given primary identifier
      */
+    @Deprecated
     public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
         Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
         synchronized (mLock) {
@@ -320,6 +322,29 @@
     }
 
     /**
+     * Returns the program list with a specified primary identifier.
+     *
+     * @param id primary identifier of a program to fetch
+     * @return the program info list with the primary identifier, or empty list if there is no such
+     * program identifier on the list
+     * @throws NullPointerException if primary identifier is {@code null}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public @NonNull List<RadioManager.ProgramInfo> getProgramInfos(
+            @NonNull ProgramSelector.Identifier id) {
+        Objects.requireNonNull(id, "Primary identifier can not be null");
+        ArrayMap<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
+        synchronized (mLock) {
+            entries = mPrograms.get(id);
+        }
+
+        if (entries == null) {
+            return new ArrayList<>();
+        }
+        return new ArrayList<>(entries.values());
+    }
+
+    /**
      * Filter for the program list.
      */
     public static final class Filter implements Parcelable {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 12442ba..c7ec052 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -16,6 +16,7 @@
 
 package android.hardware.radio;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -124,6 +125,86 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProgramType {}
 
+    /**
+     * Bitmask for HD radio subchannel 1
+     *
+     * <p>There are at most 8 HD radio subchannels of 1-based om HD radio standard. It is
+     * converted to 0-based index. 0 is the index of main program service (MPS). 1 to 7 are
+     * indexes of additional supplemental program services (SPS).
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_1 = 1 << 0;
+
+    /**
+     * Bitmask for HD radio subchannel 2
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_2 = 1 << 1;
+
+    /**
+     * Bitmask for HD radio subchannel 3
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_3 = 1 << 2;
+
+    /**
+     * Bitmask for HD radio subchannel 4
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_4 = 1 << 3;
+
+    /**
+     * Bitmask for HD radio subchannel 5
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_5 = 1 << 4;
+
+    /**
+     * Bitmask for HD radio subchannel 6
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_6 = 1 << 5;
+
+    /**
+     * Bitmask for HD radio subchannel 7
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_7 = 1 << 6;
+
+    /**
+     * Bitmask for HD radio subchannel 8
+     *
+     * <p>For further reference, see {@link #SUB_CHANNEL_HD_1}
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int SUB_CHANNEL_HD_8 = 1 << 7;
+
+    /** @hide */
+    @IntDef(prefix = { "SUB_CHANNEL_HD_" }, value = {
+            SUB_CHANNEL_HD_1,
+            SUB_CHANNEL_HD_2,
+            SUB_CHANNEL_HD_3,
+            SUB_CHANNEL_HD_4,
+            SUB_CHANNEL_HD_5,
+            SUB_CHANNEL_HD_6,
+            SUB_CHANNEL_HD_7,
+            SUB_CHANNEL_HD_8,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HdSubChannel {}
+
     public static final int IDENTIFIER_TYPE_INVALID = 0;
     /**
      * Primary identifier for analog (without RDS) AM/FM stations:
@@ -147,19 +228,15 @@
      *
      * <p>Consists of (from the LSB):
      * <li>
-     *     <ul>32bit: Station ID number.
-     *     <ul>4bit: HD_SUBCHANNEL.
-     *     <ul>18bit: AMFM_FREQUENCY.
+     *     <ul>32bit: Station ID number.</ul>
+     *     <ul>4bit: HD subchannel, see {@link #SUB_CHANNEL_HD_1}.</ul>
+     *     <ul>18bit: AMFM_FREQUENCY.</ul>
      * </li>
      *
      * <p>While station ID number should be unique globally, it sometimes gets
      * abused by broadcasters (i.e. not being set at all). To ensure local
      * uniqueness, AMFM_FREQUENCY_KHZ was added here. Global uniqueness is
-     * a best-effort - see {@link IDENTIFIER_TYPE_HD_STATION_NAME}.
-     *
-     * <p>HD Radio subchannel is a value in range of 0-7.
-     * This index is 0-based (where 0 is MPS and 1..7 are SPS),
-     * as opposed to HD Radio standard (where it's 1-based).
+     * a best-effort - see {@link #IDENTIFIER_TYPE_HD_STATION_NAME}.
      *
      * <p>The remaining bits should be set to zeros when writing on the chip side
      * and ignored when read.
@@ -202,9 +279,9 @@
      *
      * <p>Consists of (from the LSB):
      * <li>
-     *     <ul>16bit: SId.
-     *     <ul>8bit: ECC code.
-     *     <ul>4bit: SCIdS.
+     *     <ul>16bit: SId.</ul>
+     *     <ul>8bit: ECC code.</ul>
+     *     <ul>4bit: SCIdS.</ul>
      * </li>
      *
      * <p>SCIdS (Service Component Identifier within the Service) value
@@ -238,18 +315,26 @@
     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
     /**
      * 32bit primary identifier for SiriusXM Satellite Radio.
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported
      */
     public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
-    /** 0-999 range */
+    /**
+     * 0-999 range
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported
+     */
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
     /**
      * 44bit compound primary identifier for Digital Audio Broadcasting and
      * Digital Multimedia Broadcasting.
      *
      * <p>Consists of (from the LSB):
-     * - 32bit: SId;
-     * - 8bit: ECC code;
-     * - 4bit: SCIdS.
+     * <li>
+     *     <ul>32bit: SId;</ul>
+     *     <ul>8bit: ECC code;</ul>
+     *     <ul>4bit: SCIdS.</ul>
+     * </li>
      *
      * <p>SCIdS (Service Component Identifier within the Service) value
      * of 0 represents the main service, while 1 and above represents
@@ -260,6 +345,36 @@
      */
     public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14;
     /**
+     * 64bit additional identifier for HD Radio representing station location.
+     *
+     * <p>Consists of (from the LSB):
+     * <li>
+     *     <ul>4 bit: Bits 0:3 of altitude</ul>
+     *     <ul>13 bit: Fractional bits of longitude</ul>
+     *     <ul>8 bit: Integer bits of longitude</ul>
+     *     <ul>1 bit: 0 for east and 1 for west for longitude</ul>
+     *     <ul>1 bit: 0, representing longitude</ul>
+     *     <ul>5 bit: pad of zeros separating longitude and latitude</ul>
+     *     <ul>4 bit: Bits 4:7 of altitude</ul>
+     *     <ul>13 bit: Fractional bits of latitude</ul>
+     *     <ul>8 bit: Integer bits of latitude</ul>
+     *     <ul>1 bit: 0 for north and 1 for south for latitude</ul>
+     *     <ul>1 bit: 1, representing latitude</ul>
+     *     <ul>5 bit: pad of zeros</ul>
+     * </li>
+     *
+     * <p>This format is defined in NRSC-5-C document: SY_IDD_1020s.
+     *
+     * <p>Due to Station ID abuse, some
+     * {@link #IDENTIFIER_TYPE_HD_STATION_ID_EXT} identifiers may be not
+     * globally unique. To provide a best-effort solution, the station’s
+     * broadcast antenna containing the latitude and longitude may be
+     * carried as additional identifier and may be used by the tuner hardware
+     * to double-check tuning.
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15;
+    /**
      * Primary identifier for vendor-specific radio technology.
      * The value format is determined by a vendor.
      *
@@ -300,6 +415,7 @@
         IDENTIFIER_TYPE_SXM_SERVICE_ID,
         IDENTIFIER_TYPE_SXM_CHANNEL,
         IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+        IDENTIFIER_TYPE_HD_STATION_LOCATION,
     })
     @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END)
     @Retention(RetentionPolicy.SOURCE)
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 8c6083c..237ec01 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -159,12 +160,17 @@
     /**
      * Forces the analog playback for the supporting radio technology.
      *
-     * User may disable digital playback for FM HD Radio or hybrid FM/DAB with
-     * this option. This is purely user choice, ie. does not reflect digital-
+     * <p>User may disable digital playback for FM HD Radio or hybrid FM/DAB with
+     * this option. This is purely user choice, i.e. does not reflect digital-
      * analog handover state managed from the HAL implementation side.
      *
-     * Some radio technologies may not support this, ie. DAB.
+     * <p>Some radio technologies may not support this, i.e. DAB.
+     *
+     * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM}
+     * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet}
+     * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}.
      */
+    @Deprecated
     public static final int CONFIG_FORCE_ANALOG = 2;
     /**
      * Forces the digital playback for the supporting radio technology.
@@ -199,6 +205,30 @@
     /** Enables DAB-FM soft-linking (related content). */
     public static final int CONFIG_DAB_FM_SOFT_LINKING = 9;
 
+    /**
+     * Forces the FM analog playback for the supporting radio technology.
+     *
+     * <p>User may disable FM digital playback for FM HD Radio or hybrid FM/DAB
+     * with this option. This is purely user choice, i.e. does not reflect
+     * digital-analog handover state managed from the HAL implementation side.
+     *
+     * <p>Some radio technologies may not support this, i.e. DAB.
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int CONFIG_FORCE_ANALOG_FM = 10;
+
+    /**
+     * Forces the AM analog playback for the supporting radio technology.
+     *
+     * <p>User may disable FM digital playback for AM HD Radio or hybrid AM/DAB
+     * with this option. This is purely user choice, i.e. does not reflect
+     * digital-analog handover state managed from the HAL implementation side.
+     *
+     * <p>Some radio technologies may not support this, i.e. DAB.
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final int CONFIG_FORCE_ANALOG_AM = 11;
+
     /** @hide */
     @IntDef(prefix = { "CONFIG_" }, value = {
         CONFIG_FORCE_MONO,
@@ -210,6 +240,8 @@
         CONFIG_DAB_FM_LINKING,
         CONFIG_DAB_DAB_SOFT_LINKING,
         CONFIG_DAB_FM_SOFT_LINKING,
+        CONFIG_FORCE_ANALOG_FM,
+        CONFIG_FORCE_ANALOG_AM,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ConfigFlag {}
@@ -1441,6 +1473,9 @@
         private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3;
         private static final int FLAG_TUNED = 1 << 4;
         private static final int FLAG_STEREO = 1 << 5;
+        private static final int FLAG_SIGNAL_ACQUIRED = 1 << 6;
+        private static final int FLAG_HD_SIS_ACQUIRED = 1 << 7;
+        private static final int FLAG_HD_AUDIO_ACQUIRED = 1 << 8;
 
         @NonNull private final ProgramSelector mSelector;
         @Nullable private final ProgramSelector.Identifier mLogicallyTunedTo;
@@ -1595,7 +1630,7 @@
         }
 
         /**
-         * {@code true} if radio stream is not playing, ie. due to bad reception
+         * {@code true} if radio stream is not playing, i.e. due to bad reception
          * conditions or buffering. In this state volume knob MAY be disabled to
          * prevent user increasing volume too much.
          * It does NOT mean the user has muted audio.
@@ -1621,6 +1656,28 @@
         }
 
         /**
+         * @return {@code true} if the signal has been acquired.
+         */
+        @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+        public boolean isSignalAcquired() {
+            return (mInfoFlags & FLAG_SIGNAL_ACQUIRED) != 0;
+        }
+        /**
+         * @return {@code true} if HD Station Information Service (SIS) information is available.
+         */
+        @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+        public boolean isHdSisAvailable() {
+            return (mInfoFlags & FLAG_HD_SIS_ACQUIRED) != 0;
+        }
+        /**
+         * @return {@code true} if HD audio is available.
+         */
+        @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+        public boolean isHdAudioAvailable() {
+            return (mInfoFlags & FLAG_HD_AUDIO_ACQUIRED) != 0;
+        }
+
+        /**
          * Signal quality (as opposed to the name) indication from 0 (no signal)
          * to 100 (excellent)
          * @return the signal quality indication.
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index b7bf783..db14c08 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.radio;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -30,6 +31,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -142,12 +144,84 @@
     public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT =
             "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT";
 
+    /**
+     * Short context description of comment
+     *
+     * <p>Comment could relate to the current audio program content, or it might
+     * be unrelated information that the station chooses to send. It is composed
+     * of short content description and actual text (see NRSC-G200-A and id3v2.3.0
+     * for more info).
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_COMMENT_SHORT_DESCRIPTION =
+            "android.hardware.radio.metadata.COMMENT_SHORT_DESCRIPTION";
+
+    /**
+     * Actual text of comment
+     *
+     * @see #METADATA_KEY_COMMENT_SHORT_DESCRIPTION
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_COMMENT_ACTUAL_TEXT =
+            "android.hardware.radio.metadata.COMMENT_ACTUAL_TEXT";
+
+    /**
+     * Commercial
+     *
+     * <p>Commercial is application specific and generally used to facilitate the
+     * sale of products and services (see NRSC-G200-A and id3v2.3.0 for more info).
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_COMMERCIAL =
+            "android.hardware.radio.metadata.COMMERCIAL";
+
+    /**
+     * Array of Unique File Identifiers
+     *
+     * <p>Unique File Identifier (UFID) can be used to transmit an alphanumeric
+     * identifier of the current content, or of an advertised product or
+     * service (see NRSC-G200-A and id3v2.3.0 for more info).
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_UFIDS = "android.hardware.radio.metadata.UFIDS";
+
+    /**
+     * HD short station name or HD universal short station name
+     *
+     * <p>It can be up to 12 characters (see SY_IDD_1020s for more info).
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_HD_STATION_NAME_SHORT =
+            "android.hardware.radio.metadata.HD_STATION_NAME_SHORT";
+
+    /**
+     * HD long station name, HD station slogan or HD station message
+     *
+     * <p>(see SY_IDD_1020s for more info)
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_HD_STATION_NAME_LONG =
+            "android.hardware.radio.metadata.HD_STATION_NAME_LONG";
+
+    /**
+     * Bit mask of all HD Radio subchannels available
+     *
+     * <p>Bit {@link ProgramSelector#SUB_CHANNEL_HD_1} from LSB represents the
+     * availability of HD-1 subchannel (main program service, MPS). Bits
+     * {@link ProgramSelector#SUB_CHANNEL_HD_2} to {@link ProgramSelector#SUB_CHANNEL_HD_8}
+     * from LSB represent HD-2 to HD-8 subchannel (supplemental program services, SPS)
+     * respectively.
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    public static final String METADATA_KEY_HD_SUBCHANNELS_AVAILABLE =
+            "android.hardware.radio.metadata.HD_SUBCHANNELS_AVAILABLE";
 
     private static final int METADATA_TYPE_INVALID = -1;
     private static final int METADATA_TYPE_INT = 0;
     private static final int METADATA_TYPE_TEXT = 1;
     private static final int METADATA_TYPE_BITMAP = 2;
     private static final int METADATA_TYPE_CLOCK = 3;
+    private static final int METADATA_TYPE_TEXT_ARRAY = 4;
 
     private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
 
@@ -172,6 +246,13 @@
         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME_SHORT, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME, METADATA_TYPE_TEXT);
         METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME_SHORT, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_SHORT_DESCRIPTION, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_COMMENT_ACTUAL_TEXT, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_COMMERCIAL, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_UFIDS, METADATA_TYPE_TEXT_ARRAY);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_SHORT, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_HD_STATION_NAME_LONG, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_HD_SUBCHANNELS_AVAILABLE, METADATA_TYPE_INT);
     }
 
     // keep in sync with: system/media/radio/include/system/radio_metadata.h
@@ -288,9 +369,12 @@
             return false;
         }
         for (String key : mBundle.keySet()) {
-            // This logic will return a false negative if we ever put Bundles into mBundle. As of
-            // 2019-04-09, we only put ints, Strings, and Parcelables in, so it's fine for now.
-            if (!mBundle.get(key).equals(otherBundle.get(key))) {
+            if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key),
+                    METADATA_TYPE_TEXT_ARRAY)) {
+                if (!Arrays.equals(mBundle.getStringArray(key), otherBundle.getStringArray(key))) {
+                    return false;
+                }
+            } else if (!Objects.equals(mBundle.get(key), otherBundle.get(key))) {
                 return false;
             }
         }
@@ -326,7 +410,21 @@
 
             sb.append(keyDisp);
             sb.append('=');
-            sb.append(mBundle.get(key));
+            if (Flags.hdRadioImproved() && Objects.equals(METADATA_KEYS_TYPE.get(key),
+                    METADATA_TYPE_TEXT_ARRAY)) {
+                String[] stringArrayValue = mBundle.getStringArray(key);
+                sb.append('[');
+                for (int i = 0; i < stringArrayValue.length; i++) {
+                    if (i != 0) {
+                        sb.append(',');
+                    }
+                    sb.append(stringArrayValue[i]);
+                }
+                sb.append(']');
+            } else {
+                sb.append(mBundle.get(key));
+            }
+
         }
 
         sb.append("]");
@@ -427,6 +525,36 @@
         return clock;
     }
 
+    /**
+     * Gets the string array value associated with the given key as a string
+     * array.
+     *
+     * <p>Only string array keys may be used with this method:
+     * <ul>
+     * <li>{@link #METADATA_KEY_UFIDS}</li>
+     * </ul>
+     *
+     * @param key The key the value is stored under
+     * @return String array of the given string-array-type key
+     * @throws NullPointerException if metadata key is {@code null}
+     * @throws IllegalArgumentException if the metadata with the key is not found in
+     * metadata or the key is not of string-array type
+     */
+    @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+    @NonNull
+    public String[] getStringArray(@NonNull String key) {
+        Objects.requireNonNull(key, "Metadata key can not be null");
+        if (!Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) {
+            throw new IllegalArgumentException("Failed to retrieve key " + key
+                    + " as string array");
+        }
+        String[] stringArrayValue = mBundle.getStringArray(key);
+        if (stringArrayValue == null) {
+            throw new IllegalArgumentException("Key " + key + " is not found in metadata");
+        }
+        return stringArrayValue;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -539,6 +667,11 @@
          * <li>{@link #METADATA_KEY_ARTIST}</li>
          * <li>{@link #METADATA_KEY_ALBUM}</li>
          * <li>{@link #METADATA_KEY_GENRE}</li>
+         * <li>{@link #METADATA_KEY_COMMENT_SHORT_DESCRIPTION}</li>
+         * <li>{@link #METADATA_KEY_COMMENT_ACTUAL_TEXT}</li>
+         * <li>{@link #METADATA_KEY_COMMERCIAL}</li>
+         * <li>{@link #METADATA_KEY_HD_STATION_NAME_SHORT}</li>
+         * <li>{@link #METADATA_KEY_HD_STATION_NAME_LONG}</li>
          * </ul>
          *
          * @param key The key for referencing this value
@@ -563,6 +696,7 @@
          * <li>{@link #METADATA_KEY_RDS_PI}</li>
          * <li>{@link #METADATA_KEY_RDS_PTY}</li>
          * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
+         * <li>{@link #METADATA_KEY_HD_SUBCHANNELS_AVAILABLE}</li>
          * </ul>
          * or any bitmap represented by its identifier.
          *
@@ -621,6 +755,35 @@
         }
 
         /**
+         * Put a String array into the meta data. Custom keys may be used, but if
+         * the METADATA_KEYs defined in this class are used they may only be one
+         * of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_UFIDS}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The String value to store
+         * @return the same Builder instance
+         * @throws NullPointerException if key or value is null
+         * @throws IllegalArgumentException if the key is not string-array-type key
+         */
+        @FlaggedApi(Flags.FLAG_HD_RADIO_IMPROVED)
+        @NonNull
+        public Builder putStringArray(@NonNull String key, @NonNull String[] value) {
+            Objects.requireNonNull(key, "Key can not be null");
+            Objects.requireNonNull(value, "Value can not be null");
+            if (!METADATA_KEYS_TYPE.containsKey(key)
+                    || !Objects.equals(METADATA_KEYS_TYPE.get(key), METADATA_TYPE_TEXT_ARRAY)) {
+                throw new IllegalArgumentException("The " + key
+                        + " key cannot be used to put a RadioMetadata String Array.");
+            }
+            mBundle.putStringArray(key, value);
+            return this;
+        }
+
+
+        /**
          * Creates a {@link RadioMetadata} instance with the specified fields.
          *
          * @return a new {@link RadioMetadata} object
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index bdbca91..ba31ca3 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -363,7 +363,7 @@
     @Override
     public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) {
         try {
-            return mTuner.isConfigFlagSet(flag);
+            return mTuner.isConfigFlagSet(convertForceAnalogConfigFlag(flag));
         } catch (RemoteException e) {
             throw new RuntimeException("Service died", e);
         }
@@ -372,7 +372,7 @@
     @Override
     public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {
         try {
-            mTuner.setConfigFlag(flag, value);
+            mTuner.setConfigFlag(convertForceAnalogConfigFlag(flag), value);
         } catch (RemoteException e) {
             throw new RuntimeException("Service died", e);
         }
@@ -411,4 +411,13 @@
             return false;
         }
     }
+
+    private @RadioManager.ConfigFlag int convertForceAnalogConfigFlag(
+            @RadioManager.ConfigFlag int flag) throws RemoteException {
+        if (Flags.hdRadioImproved() && flag == RadioManager.CONFIG_FORCE_ANALOG
+                && mTuner.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) {
+            flag = RadioManager.CONFIG_FORCE_ANALOG_FM;
+        }
+        return flag;
+    }
 }
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index cf35460..a1d2dcc 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -17,7 +17,7 @@
 package android.os;
 
 import static android.Manifest.permission.PACKAGE_USAGE_STATS;
-import static android.Manifest.permission.READ_LOGS;
+import static android.Manifest.permission.READ_DROPBOX_DATA;
 
 import android.annotation.BytesLong;
 import android.annotation.CurrentTimeMillisLong;
@@ -81,9 +81,12 @@
 
     /**
      * Broadcast Action: This is broadcast when a new entry is added in the dropbox.
-     * You must hold the {@link android.Manifest.permission#READ_LOGS} permission
-     * in order to receive this broadcast. This broadcast can be rate limited for low priority
-     * entries
+     * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and later, you
+     * must hold the {@link android.Manifest.permission#READ_DROPBOX_DATA} permission
+     * in order to receive this broadcast. For apps targeting Android versions lower
+     * than {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, you must hold
+     * {@link android.Manifest.permission#READ_LOGS}.
+     * This broadcast can be rate limited for low priority entries
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
@@ -382,12 +385,17 @@
     /**
      * Gets the next entry from the drop box <em>after</em> the specified time.
      * You must always call {@link Entry#close()} on the return value!
+     * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission is
+     * required for apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+     * and later. {@link android.Manifest.permission#READ_LOGS} permission is
+     * required for apps targeting Android versions lower than
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
      *
      * @param tag of entry to look for, null for all tags
      * @param msec time of the last entry seen
      * @return the next entry, or null if there are no more entries
      */
-    @RequiresPermission(allOf = { READ_LOGS, PACKAGE_USAGE_STATS })
+    @RequiresPermission(allOf = { READ_DROPBOX_DATA, PACKAGE_USAGE_STATS })
     public @Nullable Entry getNextEntry(String tag, long msec) {
         try {
             return mService.getNextEntryWithAttribution(tag, msec, mContext.getOpPackageName(),
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 81d4e3a..47b6d8d 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -2367,14 +2367,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) {
             }
         }
@@ -2387,13 +2387,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/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 369a193..3ecf74e 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 ee387e7..2d1802a 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1589,28 +1589,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();
         }
@@ -1637,17 +1673,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/provider/Settings.java b/core/java/android/provider/Settings.java
index f0906b1..4bb401a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11653,6 +11653,15 @@
                 "accessibility_magnification_joystick_enabled";
 
         /**
+         * Setting that specifies whether the display magnification is enabled via a system-wide
+         * two fingers triple tap gesture.
+         *
+         * @hide
+         */
+        public static final String ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED =
+                "accessibility_magnification_two_finger_triple_tap_enabled";
+
+        /**
          * Controls magnification enable gesture. Accessibility magnification can have one or more
          * enable gestures.
          *
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index 2931435..52d4d47 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -7,3 +7,11 @@
   bug: "284297289"
 }
 
+flag {
+  name: "notification_lifetime_extension_refactor"
+    namespace: "systemui"
+    description: "Enables moving notification lifetime extension management from SystemUI to "
+        "Notification Manager Service"
+    bug: "299448097"
+}
+
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 517ae4f..5f6a9bd 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -457,12 +457,21 @@
         } else {
             hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
         }
+        LineBreakConfig config = params.getLineBreakConfig();
+        if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+                && pct.getParagraphCount() != 1) {
+            // If the text has multiple paragraph, resolve line break word style auto to none.
+            config = new LineBreakConfig.Builder()
+                    .merge(config)
+                    .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                    .build();
+        }
         ArrayList<ParagraphInfo> result = new ArrayList<>();
         for (int i = 0; i < pct.getParagraphCount(); ++i) {
             final int paraStart = pct.getParagraphStart(i);
             final int paraEnd = pct.getParagraphEnd(i);
             result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
-                    params.getTextPaint(), params.getLineBreakConfig(), pct, paraStart, paraEnd,
+                    params.getTextPaint(), config, pct, paraStart, paraEnd,
                     params.getTextDirection(), hyphenationMode, computeLayout, true,
                     pct.getMeasuredParagraph(i), null /* no recycle */)));
         }
@@ -489,6 +498,7 @@
             hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
         }
 
+        LineBreakConfig config = null;
         int paraEnd = 0;
         for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
             paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
@@ -500,8 +510,21 @@
                 paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
             }
 
+            if (config == null) {
+                config = params.getLineBreakConfig();
+                if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+                        && !(paraStart == start && paraEnd == end)) {
+                    // If the text has multiple paragraph, resolve line break word style auto to
+                    // none.
+                    config = new LineBreakConfig.Builder()
+                            .merge(config)
+                            .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+                            .build();
+                }
+            }
+
             result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
-                    params.getTextPaint(), params.getLineBreakConfig(), text, paraStart, paraEnd,
+                    params.getTextPaint(), config, text, paraStart, paraEnd,
                     params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
                     null /* no hint */,
                     null /* no recycle */)));
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 201f680..43c38f3 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -61,3 +61,17 @@
   description: "Feature flag for preventing horizontal clipping."
   bug: "63938206"
 }
+
+flag {
+  name: "deprecate_ui_fonts"
+  namespace: "text"
+  description: "Feature flag for deprecating UI fonts. By setting true for this feature flag, the elegant text height of will be turned on by default unless explicitly setting it to false by attribute or Java API call."
+  bug: "279646685"
+}
+
+flag {
+  name: "word_style_auto"
+  namespace: "text"
+  description: "A feature flag that implements line break word style auto."
+  bug: "280005585"
+}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 1ec7c41..17a3a12 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -28,6 +28,7 @@
 import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS;
 import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH;
 import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 import static android.view.InsetsController.AnimationType;
 import static android.view.InsetsController.DEBUG;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
@@ -469,8 +470,10 @@
             }
             addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
 
-            final boolean visible = mPendingFraction == 0 && source != null
-                    ? source.isVisible()
+            // The first frame of ANIMATION_TYPE_SHOW should be invisible since it is animated from
+            // the hidden state.
+            final boolean visible = mPendingFraction == 0
+                    ? mAnimationType != ANIMATION_TYPE_SHOW
                     : !mFinished || mShownOnFinish;
 
             if (outState != null && source != null) {
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index c2b5196..d9b5b2d 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -804,7 +804,7 @@
      * Sets/removes the always on top flag for this {@code windowContainer}. See
      * {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
      * Please note that this method is only intended to be used for a
-     * {@link com.android.server.wm.DisplayArea}.
+     * {@link com.android.server.wm.Task} or {@link com.android.server.wm.DisplayArea}.
      *
      * <p>
      *     Setting always on top to {@code True} will also make the {@code windowContainer} to move
diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto
new file mode 100644
index 0000000..8c33041
--- /dev/null
+++ b/core/proto/android/app/appstartinfo.proto
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package android.app;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/proto_logging/stats/enums/app/enums.proto";
+
+/**
+ * An android.app.ApplicationStartInfo object.
+ */
+message ApplicationStartInfoProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int32 pid = 1;
+    optional int32 real_uid = 2;
+    optional int32 package_uid = 3;
+    optional int32 defining_uid = 4;
+    optional string process_name = 5;
+    optional AppStartStartupState startup_state = 6;
+    optional AppStartReasonCode reason = 7;
+    optional bytes startup_timestamps = 8;
+    optional AppStartStartType start_type = 9;
+    optional bytes start_intent = 10;
+    optional AppStartLaunchMode launch_mode = 11;
+}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4732702..5b0a502 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -99,6 +99,7 @@
         optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto accessibility_force_invert_color_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto accessibility_magnification_gesture = 53 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto accessibility_magnification_two_finger_triple_tap_enabled = 54 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Accessibility accessibility = 2;
 
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 025a57d..c5889ba 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -20,6 +20,7 @@
 
 import "frameworks/base/core/proto/android/app/activitymanager.proto";
 import "frameworks/base/core/proto/android/app/appexitinfo.proto";
+import "frameworks/base/core/proto/android/app/appstartinfo.proto";
 import "frameworks/base/core/proto/android/app/notification.proto";
 import "frameworks/base/core/proto/android/app/profilerinfo.proto";
 import "frameworks/base/core/proto/android/content/component_name.proto";
@@ -1041,3 +1042,23 @@
     }
     repeated Package packages = 2;
 }
+
+// sync with com.android.server.am.am.ProcessList.java
+message AppsStartInfoProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int64 last_update_timestamp = 1;
+    message Package {
+        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional string package_name = 1;
+        message User {
+            option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+            optional int32 uid = 1;
+            repeated .android.app.ApplicationStartInfoProto app_start_info = 2;
+        }
+        repeated User users = 2;
+    }
+    repeated Package packages = 2;
+}
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 9c0ba68..0a81209 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4565,6 +4565,12 @@
     <permission android:name="android.permission.SET_DEBUG_APP"
         android:protectionLevel="signature|privileged|development" />
 
+    <!-- Allows an application to access the data in Dropbox.
+    <p>Not for use by third-party applications.
+    @FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission") -->
+    <permission android:name="android.permission.READ_DROPBOX_DATA"
+        android:protectionLevel="signature|privileged|development" />
+
     <!-- Allows an application to set the maximum number of (not needed)
          application processes that can be running.
          <p>Not for use by third-party applications. -->
diff --git a/core/res/res/drawable-watch/ic_lock_bugreport.xml b/core/res/res/drawable-watch/ic_lock_bugreport.xml
index 66dd392..b664fe4f 100644
--- a/core/res/res/drawable-watch/ic_lock_bugreport.xml
+++ b/core/res/res/drawable-watch/ic_lock_bugreport.xml
@@ -20,12 +20,12 @@
         android:viewportHeight="24.0"
         android:tint="@android:color/white">
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="@android:color/white"
         android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="@android:color/white"
         android:pathData="M10,14h4v2h-4z"/>
     <path
-        android:fillColor="#FF000000"
+        android:fillColor="@android:color/white"
         android:pathData="M10,10h4v2h-4z"/>
 </vector>
diff --git a/core/res/res/drawable-watch/ic_lock_power_off.xml b/core/res/res/drawable-watch/ic_lock_power_off.xml
index 34bc88c..b437a4b 100644
--- a/core/res/res/drawable-watch/ic_lock_power_off.xml
+++ b/core/res/res/drawable-watch/ic_lock_power_off.xml
@@ -21,6 +21,6 @@
         android:viewportHeight="24"
         android:tint="@android:color/white">
     <path
-        android:fillColor="@android:color/black"
+        android:fillColor="@android:color/white"
         android:pathData="M11,2h2v10h-2zM18.37,5.64l-1.41,1.41c2.73,2.73 2.72,7.16 -0.01,9.89 -2.73,2.73 -7.17,2.73 -9.89,0.01 -2.73,-2.73 -2.74,-7.18 -0.01,-9.91l-1.41,-1.4c-3.51,3.51 -3.51,9.21 0.01,12.73 3.51,3.51 9.21,3.51 12.72,-0.01 3.51,-3.51 3.51,-9.2 0,-12.72z"/>
 </vector>
diff --git a/core/res/res/drawable-watch/ic_restart.xml b/core/res/res/drawable-watch/ic_restart.xml
index 24d7c34..52933aa 100644
--- a/core/res/res/drawable-watch/ic_restart.xml
+++ b/core/res/res/drawable-watch/ic_restart.xml
@@ -21,6 +21,6 @@
         android:viewportHeight="24"
         android:tint="@android:color/white">
     <path
-        android:fillColor="@android:color/black"
+        android:fillColor="@android:color/white"
         android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02c-2.83,-0.48 -5,-2.94 -5,-5.91zM20,13c0,-4.42 -3.58,-8 -8,-8 -0.06,0 -0.12,0.01 -0.18,0.01l1.09,-1.09L11.5,2.5 8,6l3.5,3.5 1.41,-1.41 -1.08,-1.08c0.06,0 0.12,-0.01 0.17,-0.01 3.31,0 6,2.69 6,6 0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93z"/>
 </vector>
diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp
index 054d10c..6be553b 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -40,6 +40,8 @@
         "androidx.test.rules",
         "truth",
         "testng",
+        "android.hardware.radio.flags-aconfig-java",
+        "flag-junit",
         "mockito-target-extended",
     ],
     libs: ["android.test.base"],
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
index d638fed..d4a88c4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java
@@ -16,8 +16,6 @@
 
 package android.hardware.radio;
 
-import static com.google.common.truth.Truth.assertWithMessage;
-
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -36,8 +34,14 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArraySet;
 
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -143,12 +147,17 @@
     @Mock
     private RadioTuner.Callback mTunerCallbackMock;
 
+    @Rule
+    public final Expect mExpect = Expect.create();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
     public void getIdentifierTypes_forFilter() {
         ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Filtered identifier types").that(filter.getIdentifierTypes())
+        mExpect.withMessage("Filtered identifier types").that(filter.getIdentifierTypes())
                 .containsExactlyElementsIn(FILTER_IDENTIFIER_TYPES);
     }
 
@@ -157,7 +166,7 @@
         ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Filtered identifiers").that(filter.getIdentifiers())
+        mExpect.withMessage("Filtered identifiers").that(filter.getIdentifiers())
                 .containsExactlyElementsIn(FILTER_IDENTIFIERS);
     }
 
@@ -166,7 +175,7 @@
         ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Filter including categories")
+        mExpect.withMessage("Filter including categories")
                 .that(filter.areCategoriesIncluded()).isEqualTo(INCLUDE_CATEGORIES);
     }
 
@@ -175,7 +184,7 @@
         ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Filter excluding modifications")
+        mExpect.withMessage("Filter excluding modifications")
                 .that(filter.areModificationsExcluded()).isEqualTo(EXCLUDE_MODIFICATIONS);
     }
 
@@ -184,7 +193,7 @@
         ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Filter vendor obtained from filter without vendor filter")
+        mExpect.withMessage("Filter vendor obtained from filter without vendor filter")
                 .that(filter.getVendorFilter()).isNull();
     }
 
@@ -192,13 +201,13 @@
     public void getVendorFilter_forFilterWithVendorFilter() {
         ProgramList.Filter vendorFilter = new ProgramList.Filter(VENDOR_FILTER);
 
-        assertWithMessage("Filter vendor obtained from filter with vendor filter")
+        mExpect.withMessage("Filter vendor obtained from filter with vendor filter")
                 .that(vendorFilter.getVendorFilter()).isEqualTo(VENDOR_FILTER);
     }
 
     @Test
     public void describeContents_forFilter() {
-        assertWithMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0);
+        mExpect.withMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0);
     }
 
     @Test
@@ -206,7 +215,7 @@
         ProgramList.Filter filterCompared = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
                 FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
 
-        assertWithMessage("Hash code of the same filter")
+        mExpect.withMessage("Hash code of the same filter")
                 .that(filterCompared.hashCode()).isEqualTo(TEST_FILTER.hashCode());
     }
 
@@ -214,7 +223,7 @@
     public void hashCode_withDifferentFilters_notEquals() {
         ProgramList.Filter filterCompared = new ProgramList.Filter();
 
-        assertWithMessage("Hash code of the different filter")
+        mExpect.withMessage("Hash code of the different filter")
                 .that(filterCompared.hashCode()).isNotEqualTo(TEST_FILTER.hashCode());
     }
 
@@ -227,7 +236,7 @@
 
         ProgramList.Filter filterFromParcel =
                 ProgramList.Filter.CREATOR.createFromParcel(parcel);
-        assertWithMessage("Filter created from parcel")
+        mExpect.withMessage("Filter created from parcel")
                 .that(filterFromParcel).isEqualTo(TEST_FILTER);
     }
 
@@ -235,36 +244,37 @@
     public void newArray_forFilterCreator() {
         ProgramList.Filter[] filters = ProgramList.Filter.CREATOR.newArray(CREATOR_ARRAY_SIZE);
 
-        assertWithMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE);
+        mExpect.withMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE);
     }
 
     @Test
     public void isPurge_forChunk() {
-        assertWithMessage("Puring chunk").that(FM_DAB_ADD_CHUNK.isPurge()).isEqualTo(IS_PURGE);
+        mExpect.withMessage("Puring chunk").that(FM_DAB_ADD_CHUNK.isPurge()).isEqualTo(IS_PURGE);
     }
 
     @Test
     public void isComplete_forChunk() {
-        assertWithMessage("Complete chunk").that(FM_DAB_ADD_CHUNK.isComplete())
+        mExpect.withMessage("Complete chunk").that(FM_DAB_ADD_CHUNK.isComplete())
                 .isEqualTo(IS_COMPLETE);
     }
 
     @Test
     public void getModified_forChunk() {
-        assertWithMessage("Modified program info in chunk")
+        mExpect.withMessage("Modified program info in chunk")
                 .that(FM_DAB_ADD_CHUNK.getModified())
                 .containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2);
     }
 
     @Test
     public void getRemoved_forChunk() {
-        assertWithMessage("Removed program identifiers in chunk")
+        mExpect.withMessage("Removed program identifiers in chunk")
                 .that(FM_DAB_ADD_CHUNK.getRemoved()).containsExactly(RDS_UNIQUE_IDENTIFIER);
     }
 
     @Test
     public void describeContents_forChunk() {
-        assertWithMessage("Chunk contents").that(FM_DAB_ADD_CHUNK.describeContents()).isEqualTo(0);
+        mExpect.withMessage("Chunk contents").that(FM_DAB_ADD_CHUNK.describeContents())
+                .isEqualTo(0);
     }
 
     @Test
@@ -276,7 +286,7 @@
 
         ProgramList.Chunk chunkFromParcel =
                 ProgramList.Chunk.CREATOR.createFromParcel(parcel);
-        assertWithMessage("Chunk created from parcel")
+        mExpect.withMessage("Chunk created from parcel")
                 .that(chunkFromParcel).isEqualTo(FM_DAB_ADD_CHUNK);
     }
 
@@ -284,7 +294,7 @@
     public void newArray_forChunkCreator() {
         ProgramList.Chunk[] chunks = ProgramList.Chunk.CREATOR.newArray(CREATOR_ARRAY_SIZE);
 
-        assertWithMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE);
+        mExpect.withMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE);
     }
 
     @Test
@@ -295,7 +305,7 @@
         IllegalStateException thrown = assertThrows(IllegalStateException.class,
                 () -> mRadioTuner.getProgramList(parameters));
 
-        assertWithMessage("Exception for getting program list when not ready")
+        mExpect.withMessage("Exception for getting program list when not ready")
                 .that(thrown).hasMessageThat().contains("Program list is not ready yet");
     }
 
@@ -308,7 +318,7 @@
         RuntimeException thrown = assertThrows(RuntimeException.class,
                 () -> mRadioTuner.getProgramList(parameters));
 
-        assertWithMessage("Exception for getting program list when service is dead")
+        mExpect.withMessage("Exception for getting program list when service is dead")
                 .that(thrown).hasMessageThat().contains("Service died");
     }
 
@@ -330,7 +340,7 @@
 
         ProgramList nullProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
 
-        assertWithMessage("Exception for radio HAL client not supporting program list")
+        mExpect.withMessage("Exception for radio HAL client not supporting program list")
                 .that(nullProgramList).isNull();
     }
 
@@ -344,7 +354,7 @@
             mRadioTuner.getDynamicProgramList(TEST_FILTER);
         });
 
-        assertWithMessage("Exception for radio HAL client service died")
+        mExpect.withMessage("Exception for radio HAL client service died")
                 .that(thrown).hasMessageThat().contains("Service died");
     }
 
@@ -360,7 +370,7 @@
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(DAB_DMB_SID_EXT_IDENTIFIER);
         verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT).onComplete();
-        assertWithMessage("Program info in program list after adding FM and DAB info")
+        mExpect.withMessage("Program info in program list after adding FM and DAB info")
                 .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_1,
                         DAB_PROGRAM_INFO_2);
     }
@@ -378,7 +388,7 @@
         mTunerCallback.onProgramListUpdated(fmRemovedChunk);
 
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
-        assertWithMessage("Program info in program list after removing FM id")
+        mExpect.withMessage("Program info in program list after removing FM id")
                 .that(mProgramList.toList()).containsExactly(DAB_PROGRAM_INFO_1,
                         DAB_PROGRAM_INFO_2);
     }
@@ -397,7 +407,7 @@
 
         verify(mListCallbackMocks[0], after(TIMEOUT_MS).never()).onItemRemoved(
                 DAB_DMB_SID_EXT_IDENTIFIER);
-        assertWithMessage("Program info in program list after removing part of DAB ids")
+        mExpect.withMessage("Program info in program list after removing part of DAB ids")
                 .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, DAB_PROGRAM_INFO_2);
     }
 
@@ -419,7 +429,7 @@
         mTunerCallback.onProgramListUpdated(dabRemovedChunk2);
 
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(DAB_DMB_SID_EXT_IDENTIFIER);
-        assertWithMessage("Program info in program list after removing all DAB ids")
+        mExpect.withMessage("Program info in program list after removing all DAB ids")
                 .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO);
     }
 
@@ -448,7 +458,7 @@
 
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
         verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(DAB_DMB_SID_EXT_IDENTIFIER);
-        assertWithMessage("Program list after purge chunk applied")
+        mExpect.withMessage("Program list after purge chunk applied")
                 .that(mProgramList.toList()).isEmpty();
     }
 
@@ -607,6 +617,49 @@
         verify(mTunerMock, CALLBACK_TIMEOUT).stopProgramListUpdates();
     }
 
+    @Test
+    public void get() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+
+        mExpect.withMessage(
+                "FM program info in program list after updating with chunk of FM program")
+                .that(mProgramList.get(FM_IDENTIFIER)).isEqualTo(FM_PROGRAM_INFO);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getProgramInfos() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        mTunerCallback.onProgramListUpdated(FM_DAB_ADD_CHUNK);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(DAB_DMB_SID_EXT_IDENTIFIER);
+
+        mExpect.withMessage("FM program info in program list")
+                .that(mProgramList.getProgramInfos(FM_IDENTIFIER)).containsExactly(FM_PROGRAM_INFO);
+        mExpect.withMessage("All DAB program info in program list")
+                .that(mProgramList.getProgramInfos(DAB_DMB_SID_EXT_IDENTIFIER))
+                .containsExactly(DAB_PROGRAM_INFO_1, DAB_PROGRAM_INFO_2);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getProgramInfos_withIdNotFound() throws Exception {
+        createRadioTuner();
+        mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+        registerListCallbacks(/* numCallbacks= */ 1);
+        mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+        verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+
+        mExpect.withMessage("DAB program info in program list")
+                .that(mProgramList.getProgramInfos(DAB_DMB_SID_EXT_IDENTIFIER)).isEmpty();
+    }
+
     private static ProgramSelector createProgramSelector(int programType,
             ProgramSelector.Identifier identifier) {
         return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index b9f4c3f..03de143 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -32,8 +32,12 @@
 import android.content.pm.ApplicationInfo;
 import android.os.Parcel;
 import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArrayMap;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -42,6 +46,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -90,10 +95,26 @@
     private static final RadioManager.ModuleProperties AMFM_PROPERTIES =
             createAmFmProperties(/* dabFrequencyTable= */ null);
 
+    private static final int DAB_INFO_FLAG_LIVE_VALUE = 1;
+    private static final int DAB_INFO_FLAG_TUNED_VALUE = 1 << 4;
+    private static final int DAB_INFO_FLAG_STEREO_VALUE = 1 << 5;
+    private static final int HD_INFO_FLAG_LIVE_VALUE = 1;
+    private static final int HD_INFO_FLAG_TUNED_VALUE = 1 << 4;
+    private static final int HD_INFO_FLAG_STEREO_VALUE = 1 << 5;
+    private static final int HD_INFO_FLAG_SIGNAL_ACQUISITION_VALUE = 1 << 6;
+    private static final int HD_INFO_FLAG_SIS_ACQUISITION_VALUE = 1 << 7;
     /**
-     * Info flags with live, tuned and stereo enabled
+     * Info flags with live, tuned, and stereo enabled for DAB program
      */
-    private static final int INFO_FLAGS = 0b110001;
+    private static final int INFO_FLAGS_DAB = DAB_INFO_FLAG_LIVE_VALUE | DAB_INFO_FLAG_TUNED_VALUE
+            | DAB_INFO_FLAG_STEREO_VALUE;
+    /**
+     * HD program info flags with live, tuned, stereo enabled, signal acquired, SIS information
+     * available but audio unavailable
+     */
+    private static final int INFO_FLAGS_HD = HD_INFO_FLAG_LIVE_VALUE | HD_INFO_FLAG_TUNED_VALUE
+            | HD_INFO_FLAG_STEREO_VALUE | HD_INFO_FLAG_SIGNAL_ACQUISITION_VALUE
+            | HD_INFO_FLAG_SIS_ACQUISITION_VALUE;
     private static final int SIGNAL_QUALITY = 2;
     private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
@@ -112,9 +133,20 @@
                     new ProgramSelector.Identifier[]{
                             DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER},
                     /* vendorIds= */ null);
+
+    private static final long HD_FREQUENCY = 97_100;
+    private static final ProgramSelector.Identifier HD_STATION_EXT_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT,
+                    /* value= */ (HD_FREQUENCY << 36) | 0x1L);
+    private static final ProgramSelector HD_SELECTOR = new ProgramSelector(
+            ProgramSelector.PROGRAM_TYPE_FM_HD, HD_STATION_EXT_IDENTIFIER,
+            new ProgramSelector.Identifier[]{}, /* vendorIds= */ null);
+
     private static final RadioMetadata METADATA = createMetadata();
     private static final RadioManager.ProgramInfo DAB_PROGRAM_INFO =
             createDabProgramInfo(DAB_SELECTOR);
+    private static final RadioManager.ProgramInfo HD_PROGRAM_INFO = createHdProgramInfo(
+            HD_SELECTOR);
 
     private static final int EVENT_ANNOUNCEMENT_TYPE = Announcement.TYPE_EVENT;
     private static final List<Announcement> TEST_ANNOUNCEMENT_LIST = Arrays.asList(
@@ -135,6 +167,9 @@
     @Mock
     private ICloseHandle mCloseHandleMock;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
     public void getType_forBandDescriptor() {
         RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
@@ -927,6 +962,27 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isSignalAcquired_forProgramInfo() {
+        assertWithMessage("Signal acquisition status for HD program info")
+                .that(HD_PROGRAM_INFO.isSignalAcquired()).isTrue();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isHdSisAvailable_forProgramInfo() {
+        assertWithMessage("SIS information acquisition status for HD program")
+                .that(HD_PROGRAM_INFO.isHdSisAvailable()).isTrue();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isHdAudioAvailable_forProgramInfo() {
+        assertWithMessage("Audio acquisition status for HD program")
+                .that(HD_PROGRAM_INFO.isHdAudioAvailable()).isFalse();
+    }
+
+    @Test
     public void getSignalStrength_forProgramInfo() {
         assertWithMessage("Signal strength of DAB program info")
                 .that(DAB_PROGRAM_INFO.getSignalStrength()).isEqualTo(SIGNAL_QUALITY);
@@ -1156,9 +1212,18 @@
     }
 
     private static RadioManager.ProgramInfo createDabProgramInfo(ProgramSelector selector) {
-        return new RadioManager.ProgramInfo(selector, DAB_SID_EXT_IDENTIFIER,
-                DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS,
-                SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null);
+        return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(),
+                DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED),
+                INFO_FLAGS_DAB, SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null);
+    }
+
+    private static RadioManager.ProgramInfo createHdProgramInfo(ProgramSelector selector) {
+        long frequency = (selector.getPrimaryId().getValue() >> 32);
+        ProgramSelector.Identifier physicallyTunedToId = new ProgramSelector.Identifier(
+                ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, frequency);
+        return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(), physicallyTunedToId,
+                Collections.emptyList(), INFO_FLAGS_HD, SIGNAL_QUALITY, METADATA,
+                /* vendorInfo= */ null);
     }
 
     private void createRadioManager() throws RemoteException {
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
index e348a51..3891acc 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
@@ -22,12 +22,18 @@
 
 import android.graphics.Bitmap;
 import android.os.Parcel;
+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 org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.Arrays;
 import java.util.Set;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -35,6 +41,8 @@
 
     private static final int CREATOR_ARRAY_SIZE = 3;
     private static final int INT_KEY_VALUE = 1;
+    private static final String ARTIST_KEY_VALUE = "artistTest";
+    private static final String[] UFIDS_VALUE = new String[]{"ufid1", "ufid2"};
     private static final long TEST_UTC_SECOND_SINCE_EPOCH = 200;
     private static final int TEST_TIME_ZONE_OFFSET_MINUTES = 1;
 
@@ -43,6 +51,9 @@
     @Mock
     private Bitmap mBitmapValue;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Test
     public void describeContents_forClock() {
         RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH,
@@ -97,7 +108,7 @@
             mBuilder.putInt(invalidIntKey, INT_KEY_VALUE);
         });
 
-        assertWithMessage("Exception for putting illegal int-value key %s", invalidIntKey)
+        assertWithMessage("Exception for putting illegal int-value for key %s", invalidIntKey)
                 .that(thrown).hasMessageThat()
                 .matches(".*" + invalidIntKey + ".*cannot.*int.*?");
     }
@@ -117,6 +128,42 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void putStringArray_withIllegalKey_throwsException() {
+        String invalidStringArrayKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG;
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            mBuilder.putStringArray(invalidStringArrayKey, UFIDS_VALUE);
+        });
+
+        assertWithMessage("Exception for putting illegal string-array-value for key %s",
+                invalidStringArrayKey).that(thrown).hasMessageThat()
+                .matches(".*" + invalidStringArrayKey + ".*cannot.*Array.*?");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void putStringArray_withNullKey_throwsException() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+            mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE);
+        });
+
+        assertWithMessage("Exception for putting string-array with null key")
+                .that(thrown).hasMessageThat().contains("can not be null");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void putStringArray_withNullString_throwsException() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+            mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null);
+        });
+
+        assertWithMessage("Exception for putting null string-array")
+                .that(thrown).hasMessageThat().contains("can not be null");
+    }
+
+    @Test
     public void containsKey_withKeyInMetadata() {
         String key = RadioMetadata.METADATA_KEY_RDS_PI;
         RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
@@ -156,11 +203,10 @@
     @Test
     public void getString_withKeyInMetadata() {
         String key = RadioMetadata.METADATA_KEY_ARTIST;
-        String value = "artistTest";
-        RadioMetadata metadata = mBuilder.putString(key, value).build();
+        RadioMetadata metadata = mBuilder.putString(key, ARTIST_KEY_VALUE).build();
 
         assertWithMessage("String value for key %s in metadata", key)
-                .that(metadata.getString(key)).isEqualTo(value);
+                .that(metadata.getString(key)).isEqualTo(ARTIST_KEY_VALUE);
     }
 
     @Test
@@ -235,10 +281,62 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getStringArray_withKeyInMetadata() {
+        String key = RadioMetadata.METADATA_KEY_UFIDS;
+        RadioMetadata metadata = mBuilder.putStringArray(key, UFIDS_VALUE).build();
+
+        assertWithMessage("String-array value for key %s not in metadata", key)
+                .that(metadata.getStringArray(key)).asList().isEqualTo(Arrays.asList(UFIDS_VALUE));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getStringArray_withKeyNotInMetadata() {
+        String key = RadioMetadata.METADATA_KEY_UFIDS;
+        RadioMetadata metadata = mBuilder.build();
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            metadata.getStringArray(key);
+        });
+
+        assertWithMessage("Exception for getting string array for string-array value for key %s "
+                + "not in metadata", key).that(thrown).hasMessageThat().contains("not found");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getStringArray_withNullKey() {
+        RadioMetadata metadata = mBuilder.build();
+
+        NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+            metadata.getStringArray(/* key= */ null);
+        });
+
+        assertWithMessage("Exception for getting string array with null key")
+                .that(thrown).hasMessageThat().contains("can not be null");
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void getStringArray_withInvalidKey() {
+        String invalidClockKey = RadioMetadata.METADATA_KEY_HD_STATION_NAME_LONG;
+        RadioMetadata metadata = mBuilder.build();
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+            metadata.getStringArray(invalidClockKey);
+        });
+
+        assertWithMessage("Exception for getting string array for key %s not of string-array type",
+                invalidClockKey).that(thrown).hasMessageThat()
+                .contains("string array");
+    }
+
+    @Test
     public void size_withNonEmptyMetadata() {
         RadioMetadata metadata = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
-                .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
                 .build();
 
         assertWithMessage("Size of fields in non-empty metadata")
@@ -257,7 +355,7 @@
     public void keySet_withNonEmptyMetadata() {
         RadioMetadata metadata = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
-                .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
                 .putBitmap(RadioMetadata.METADATA_KEY_ICON, mBitmapValue)
                 .build();
 
@@ -291,7 +389,7 @@
     public void equals_forMetadataWithSameContents_returnsTrue() {
         RadioMetadata metadata = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
-                .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
                 .build();
         RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata);
         RadioMetadata metadataCopied = copyBuilder.build();
@@ -315,10 +413,29 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
     public void writeToParcel_forRadioMetadata() {
         RadioMetadata metadataExpected = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
-                .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
+                .build();
+        Parcel parcel = Parcel.obtain();
+
+        metadataExpected.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Radio metadata created from parcel")
+                .that(metadataFromParcel).isEqualTo(metadataExpected);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void writeToParcel_forRadioMetadata_withStringArrayTypeMetadata() {
+        RadioMetadata metadataExpected = mBuilder
+                .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
+                .putStringArray(RadioMetadata.METADATA_KEY_UFIDS, UFIDS_VALUE)
                 .build();
         Parcel parcel = Parcel.obtain();
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index 6a6a951..7ca806b 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,9 +36,14 @@
 import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.RemoteException;
+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 org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -77,6 +83,9 @@
     @Mock
     private RadioTuner.Callback mCallbackMock;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
@@ -604,6 +613,44 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogSupported()
+            throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM))
+                .thenReturn(true);
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false);
+
+        assertWithMessage("Force analog with feature flag enabled and force FM supported")
+                .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isTrue();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isConfigFlagSet_withForceAnalogWhenFmForceAnalogNotSupported()
+            throws Exception {
+        when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM))
+                .thenReturn(false);
+        when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG_FM)).thenReturn(true);
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false);
+
+        assertWithMessage("Force analog with feature flag enabled but force FM unsupported")
+                .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isFalse();
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void isConfigFlagSet_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled()
+            throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(false);
+
+        assertWithMessage("Force analog without Force FM enabled")
+                .that(mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).isFalse();
+    }
+
+    @Test
     public void isConfigFlagSet_whenServiceDied_fails() throws Exception {
         when(mTunerMock.isConfigFlagSet(anyInt())).thenThrow(new RemoteException());
 
@@ -636,6 +683,43 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void setConfigFlag_withForceAnalogWhenFmForceAnalogSupported() throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
+
+        mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false);
+
+        verify(mTunerMock, never()).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG),
+                anyBoolean());
+        verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG_FM, false);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void setConfigFlag_withForceAnalogWhenFmForceAnalogNotSupported() throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
+        when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM))
+                .thenReturn(false);
+
+        mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false);
+
+        verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, false);
+        verify(mTunerMock, never()).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG_FM),
+                anyBoolean());
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_HD_RADIO_IMPROVED)
+    public void setConfigFlag_withForceAnalogWhenHdRadioImprovedFeatureNotEnabled()
+            throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenReturn(true);
+
+        mRadioTuner.setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, /* value= */ false);
+
+        verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, false);
+    }
+
+    @Test
     public void getParameters_forTunerAdapter() throws Exception {
         List<String> parameterKeys = List.of("ParameterKeyMock");
         Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
new file mode 100644
index 0000000..2ec58d4
--- /dev/null
+++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.app.usage;
+
+import static android.view.Surface.ROTATION_90;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+
+import android.app.usage.UsageEvents.Event;
+import android.content.res.Configuration;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class ParcelableUsageEventListTest {
+    private static final int SMALL_TEST_EVENT_COUNT = 100;
+    private static final int LARGE_TEST_EVENT_COUNT = 10000;
+
+    private Random mRandom = new Random();
+
+    @Test
+    public void testSmallList() throws Exception {
+        testParcelableUsageEventList(SMALL_TEST_EVENT_COUNT);
+    }
+
+    @Test
+    public void testLargeList() throws Exception {
+        testParcelableUsageEventList(LARGE_TEST_EVENT_COUNT);
+    }
+
+    private void testParcelableUsageEventList(int eventCount) throws Exception {
+        List<Event> smallList = new ArrayList<>();
+        for (int i = 0; i < eventCount; i++) {
+            smallList.add(generateUsageEvent());
+        }
+
+        ParcelableUsageEventList slice;
+        Parcel parcel = Parcel.obtain();
+        try {
+            parcel.writeParcelable(new ParcelableUsageEventList(smallList), 0);
+            parcel.setDataPosition(0);
+            slice = parcel.readParcelable(getClass().getClassLoader(),
+                    ParcelableUsageEventList.class);
+        } finally {
+            parcel.recycle();
+        }
+
+        assertNotNull(slice);
+        assertNotNull(slice.getList());
+        assertEquals(eventCount, slice.getList().size());
+
+        for (int i = 0; i < eventCount; i++) {
+            compareUsageEvent(smallList.get(i), slice.getList().get(i));
+        }
+    }
+
+    private Event generateUsageEvent() {
+        final Event event = new Event();
+        event.mEventType = mRandom.nextInt(Event.MAX_EVENT_TYPE + 1);
+        event.mPackage = anyString();
+        event.mClass = anyString();
+        event.mTimeStamp = anyLong();
+        event.mInstanceId = anyInt();
+        event.mTimeStamp = anyLong();
+
+        switch (event.mEventType) {
+            case Event.CONFIGURATION_CHANGE:
+                event.mConfiguration = new Configuration();
+                event.mConfiguration.seq = anyInt();
+                event.mConfiguration.screenLayout = Configuration.SCREENLAYOUT_ROUND_YES;
+                event.mConfiguration.smallestScreenWidthDp = 100;
+                event.mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+                event.mConfiguration.windowConfiguration.setRotation(ROTATION_90);
+                break;
+            case Event.SHORTCUT_INVOCATION:
+                event.mShortcutId = anyString();
+                break;
+            case Event.CHOOSER_ACTION:
+                event.mAction = anyString();
+                event.mContentType = anyString();
+                event.mContentAnnotations = new String[mRandom.nextInt(10)];
+                for (int i = 0; i < event.mContentAnnotations.length; i++) {
+                    event.mContentAnnotations[i] = anyString();
+                }
+                break;
+            case Event.STANDBY_BUCKET_CHANGED:
+                event.mBucketAndReason = anyInt();
+                break;
+            case Event.NOTIFICATION_INTERRUPTION:
+                event.mNotificationChannelId = anyString();
+                break;
+            case Event.LOCUS_ID_SET:
+                event.mLocusId = anyString();
+                break;
+        }
+
+        event.mFlags = anyInt();
+        return event;
+    }
+
+    private static void compareUsageEvent(Event ue1, Event ue2) {
+        assertEquals(ue1.mPackage, ue2.mPackage);
+        assertEquals(ue1.mClass, ue2.mClass);
+        assertEquals(ue1.mTaskRootPackage, ue2.mTaskRootPackage);
+        assertEquals(ue1.mTaskRootClass, ue2.mTaskRootClass);
+        assertEquals(ue1.mInstanceId, ue2.mInstanceId);
+        assertEquals(ue1.mEventType, ue2.mEventType);
+        assertEquals(ue1.mTimeStamp, ue2.mTimeStamp);
+
+        switch (ue1.mEventType) {
+            case Event.CONFIGURATION_CHANGE:
+                assertEquals(ue1.mConfiguration, ue2.mConfiguration);
+                break;
+            case Event.SHORTCUT_INVOCATION:
+                assertEquals(ue1.mShortcutId, ue2.mShortcutId);
+                break;
+            case Event.CHOOSER_ACTION:
+                assertEquals(ue1.mAction, ue2.mAction);
+                assertEquals(ue1.mContentType, ue2.mContentType);
+                assertTrue(Arrays.equals(ue1.mContentAnnotations, ue2.mContentAnnotations));
+                break;
+            case Event.STANDBY_BUCKET_CHANGED:
+                assertEquals(ue1.mBucketAndReason, ue2.mBucketAndReason);
+                break;
+            case Event.NOTIFICATION_INTERRUPTION:
+                assertEquals(ue1.mNotificationChannelId, ue1.mNotificationChannelId);
+                break;
+            case Event.LOCUS_ID_SET:
+                assertEquals(ue1.mLocusId, ue2.mLocusId);
+                break;
+        }
+
+        assertEquals(ue1.mFlags, ue2.mFlags);
+    }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index cc73ece..35498b7 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2365,6 +2365,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
+    "33989965": {
+      "message": " Met condition %s for #%d (%d left)",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "34106798": {
       "message": "Content Recording: Display %d state was (%d), is now (%d), so update recording?",
       "level": "VERBOSE",
@@ -4483,6 +4489,12 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/AppTransitionController.java"
     },
+    "2053743391": {
+      "message": " Add condition %s for #%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "2060978050": {
       "message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d",
       "level": "WARN",
@@ -4543,6 +4555,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "2124732293": {
+      "message": "#%d: Met condition: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/Transition.java"
+    },
     "2128917433": {
       "message": "onProposedRotationChanged, rotation=%d",
       "level": "VERBOSE",
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 4eaa013..92c4de6 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -27,6 +27,9 @@
 import android.annotation.Nullable;
 import android.annotation.Px;
 import android.annotation.Size;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.fonts.FontVariationAxis;
 import android.os.Build;
@@ -614,6 +617,7 @@
         mCompatScaling = mInvCompatScaling = 1;
         setTextLocales(LocaleList.getAdjustedDefault());
         mColor = Color.pack(Color.BLACK);
+        resetElegantTextHeight();
     }
 
     /**
@@ -654,7 +658,7 @@
 
         mBidiFlags = BIDI_DEFAULT_LTR;
         setTextLocales(LocaleList.getAdjustedDefault());
-        setElegantTextHeight(false);
+        resetElegantTextHeight();
         mFontFeatureSettings = null;
         mFontVariationSettings = null;
 
@@ -1735,12 +1739,30 @@
     /**
      * Get the elegant metrics flag.
      *
+     * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by
+     * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or
+     * later.
+     *
      * @return true if elegant metrics are enabled for text drawing.
      */
     public boolean isElegantTextHeight() {
-        return nIsElegantTextHeight(mNativePaint);
+        int rawValue = nGetElegantTextHeight(mNativePaint);
+        switch (rawValue) {
+            case ELEGANT_TEXT_HEIGHT_DISABLED:
+                return false;
+            case ELEGANT_TEXT_HEIGHT_ENABLED:
+                return true;
+            case ELEGANT_TEXT_HEIGHT_UNSET:
+            default:
+                return com.android.text.flags.Flags.deprecateUiFonts();
+        }
     }
 
+    // Note: the following three values must be equal to the ones in the JNI file: Paint.cpp
+    private static final int ELEGANT_TEXT_HEIGHT_UNSET = -1;
+    private static final int ELEGANT_TEXT_HEIGHT_ENABLED = 0;
+    private static final int ELEGANT_TEXT_HEIGHT_DISABLED = 1;
+
     /**
      * Set the paint's elegant height metrics flag. This setting selects font
      * variants that have not been compacted to fit Latin-based vertical
@@ -1749,7 +1771,29 @@
      * @param elegant set the paint's elegant metrics flag for drawing text.
      */
     public void setElegantTextHeight(boolean elegant) {
-        nSetElegantTextHeight(mNativePaint, elegant);
+        nSetElegantTextHeight(mNativePaint,
+                elegant ? ELEGANT_TEXT_HEIGHT_ENABLED : ELEGANT_TEXT_HEIGHT_DISABLED);
+    }
+
+    /**
+     * A change ID for deprecating UI fonts.
+     *
+     * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by
+     * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or
+     * later.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long DEPRECATE_UI_FONT = 279646685L;
+
+    private void resetElegantTextHeight() {
+        if (CompatChanges.isChangeEnabled(DEPRECATE_UI_FONT)) {
+            nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_UNSET);
+        } else {
+            nSetElegantTextHeight(mNativePaint, ELEGANT_TEXT_HEIGHT_DISABLED);
+        }
     }
 
     /**
@@ -3660,9 +3704,9 @@
     @CriticalNative
     private static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
     @CriticalNative
-    private static native boolean nIsElegantTextHeight(long paintPtr);
+    private static native int nGetElegantTextHeight(long paintPtr);
     @CriticalNative
-    private static native void nSetElegantTextHeight(long paintPtr, boolean elegant);
+    private static native void nSetElegantTextHeight(long paintPtr, int elegant);
     @CriticalNative
     private static native float nGetTextSize(long paintPtr);
     @CriticalNative
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index dc1773b..62195856 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -17,11 +17,17 @@
 package android.graphics.text;
 
 import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+import static com.android.text.flags.Flags.FLAG_WORD_STYLE_AUTO;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+import android.os.LocaleList;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -37,6 +43,14 @@
 public final class LineBreakConfig {
 
     /**
+     * A feature ID for automatic line break word style.
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long WORD_STYLE_AUTO = 280005585L;
+
+    /**
      * No hyphenation preference is specified.
      *
      * <p>
@@ -112,8 +126,11 @@
      * </pre>
      *
      * <p>
-     * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text
-     * layout/rendering.
+     * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if the target SDK version is API
+     * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text
+     * layout/rendering. This value is resolved to {@link #LINE_BREAK_STYLE_AUTO} if the target SDK
+     * version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is
+     * used for text layout/rendering.
      */
     @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1;
@@ -154,10 +171,29 @@
     @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static final int LINE_BREAK_STYLE_NO_BREAK = 4;
 
+    /**
+     * A special value for the line breaking style option.
+     *
+     * <p>
+     * The auto option for the line break style set the line break style based on the locale of the
+     * text rendering context. You can specify the context locale by
+     * {@link android.widget.TextView#setTextLocales(LocaleList)} or
+     * {@link android.graphics.Paint#setTextLocales(LocaleList)}.
+     *
+     * <p>
+     * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings:
+     * - If at least one locale in the locale list contains Japanese script, this option is
+     * equivalent to {@link #LINE_BREAK_STYLE_STRICT}.
+     * - Otherwise, this option is equivalent to {@link #LINE_BREAK_STYLE_NONE}.
+     */
+    @FlaggedApi(FLAG_WORD_STYLE_AUTO)
+    public static final int LINE_BREAK_STYLE_AUTO = 5;
+
     /** @hide */
     @IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = {
             LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL,
-            LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK
+            LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED, LINE_BREAK_STYLE_NO_BREAK,
+            LINE_BREAK_STYLE_AUTO
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface LineBreakStyle {}
@@ -183,8 +219,11 @@
      *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
      * </pre>
      *
-     * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if this value is used for
-     * text layout/rendering.
+     * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if the target SDK version is
+     * API {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or before and this value is used for text
+     * layout/rendering. This value is resolved to {@link #LINE_BREAK_WORD_STYLE_AUTO} if the target
+     * SDK version is API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or after and this value is
+     * used for text layout/rendering.
      */
     @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1;
@@ -204,9 +243,29 @@
      */
     public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
 
+    /**
+     * A special value for the line breaking word style option.
+     *
+     * <p>
+     * The auto option for the line break word style does some heuristics based on locales and line
+     * count.
+     *
+     * <p>
+     * In the API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, auto option does followings:
+     * - If at least one locale in the locale list contains Korean script, this option is equivalent
+     * to {@link #LINE_BREAK_WORD_STYLE_PHRASE}.
+     * - If not, then if at least one locale in the locale list contains Japanese script, this
+     * option is equivalent to {@link #LINE_BREAK_WORD_STYLE_PHRASE} if the result of its line
+     * count is less than 5 lines.
+     * - Otherwise, this option is equivalent to {@link #LINE_BREAK_WORD_STYLE_NONE}.
+     */
+    @FlaggedApi(FLAG_WORD_STYLE_AUTO)
+    public static final int LINE_BREAK_WORD_STYLE_AUTO = 2;
+
     /** @hide */
     @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = {
-        LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED
+        LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED,
+            LINE_BREAK_WORD_STYLE_AUTO
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface LineBreakWordStyle {}
@@ -425,11 +484,13 @@
      * @hide
      */
     public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) {
+        final int defaultStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
+                ? LINE_BREAK_STYLE_AUTO : LINE_BREAK_STYLE_NONE;
         if (config == null) {
-            return LINE_BREAK_STYLE_NONE;
+            return defaultStyle;
         }
         return config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
-                ? LINE_BREAK_STYLE_NONE : config.mLineBreakStyle;
+                ? defaultStyle : config.mLineBreakStyle;
     }
 
     /**
@@ -451,11 +512,13 @@
      */
     public static @LineBreakWordStyle int getResolvedLineBreakWordStyle(
             @Nullable LineBreakConfig config) {
+        final int defaultWordStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
+                ? LINE_BREAK_WORD_STYLE_AUTO : LINE_BREAK_WORD_STYLE_NONE;
         if (config == null) {
-            return LINE_BREAK_WORD_STYLE_NONE;
+            return defaultWordStyle;
         }
         return config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED
-                ? LINE_BREAK_WORD_STYLE_NONE : config.mLineBreakWordStyle;
+                ? defaultWordStyle : config.mLineBreakWordStyle;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 7c0d0e3..51c71b1 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -20,3 +20,10 @@
     description: "Enables desktop windowing"
     bug: "304778354"
 }
+
+flag {
+    name: "enable_split_contextual"
+    namespace: "multitasking"
+    description: "Enables invoking split contextually"
+    bug: "276361926"
+}
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index ffb329d..00d049c 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -33,6 +33,14 @@
 #endif  // __ANDROID__
 }
 
+inline bool deprecate_ui_fonts() {
+#ifdef __ANDROID__
+    return com_android_text_flags_deprecate_ui_fonts();
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
 }  // namespace text_feature
 
 }  // namespace android
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index bcfb4c8..7552b56d 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -16,12 +16,15 @@
 
 #include "MinikinUtils.h"
 
-#include <string>
-
 #include <log/log.h>
-
+#include <minikin/FamilyVariant.h>
 #include <minikin/MeasuredText.h>
 #include <minikin/Measurement.h>
+
+#include <optional>
+#include <string>
+
+#include "FeatureFlags.h"
 #include "Paint.h"
 #include "SkPathMeasure.h"
 #include "Typeface.h"
@@ -43,9 +46,17 @@
     minikinPaint.wordSpacing = paint->getWordSpacing();
     minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
     minikinPaint.localeListId = paint->getMinikinLocaleListId();
-    minikinPaint.familyVariant = paint->getFamilyVariant();
     minikinPaint.fontStyle = resolvedFace->fStyle;
     minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
+
+    const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
+    if (familyVariant.has_value()) {
+        minikinPaint.familyVariant = familyVariant.value();
+    } else {
+        minikinPaint.familyVariant = text_feature::deprecate_ui_fonts()
+                                             ? minikin::FamilyVariant::ELEGANT
+                                             : minikin::FamilyVariant::DEFAULT;
+    }
     return minikinPaint;
 }
 
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 4a8f3e1..caffdfc 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -94,9 +94,10 @@
 
     uint32_t getMinikinLocaleListId() const { return mMinikinLocaleListId; }
 
+    void resetFamilyVariant() { mFamilyVariant.reset(); }
     void setFamilyVariant(minikin::FamilyVariant variant) { mFamilyVariant = variant; }
 
-    minikin::FamilyVariant getFamilyVariant() const { return mFamilyVariant; }
+    std::optional<minikin::FamilyVariant> getFamilyVariant() const { return mFamilyVariant; }
 
     void setStartHyphenEdit(uint32_t startHyphen) {
         mHyphenEdit = minikin::packHyphenEdit(
@@ -171,7 +172,7 @@
     float mWordSpacing = 0;
     std::string mFontFeatureSettings;
     uint32_t mMinikinLocaleListId;
-    minikin::FamilyVariant mFamilyVariant;
+    std::optional<minikin::FamilyVariant> mFamilyVariant;
     uint32_t mHyphenEdit = 0;
     // The native Typeface object has the same lifetime of the Java Typeface
     // object. The Java Paint object holds a strong reference to the Java Typeface
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 1463945..8c71d6f 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -935,15 +935,39 @@
         obj->setMinikinLocaleListId(minikinLocaleListId);
     }
 
-    static jboolean isElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
+    // Note: Following three values must be equal to the ones in Java file: Paint.java.
+    constexpr jint ELEGANT_TEXT_HEIGHT_UNSET = -1;
+    constexpr jint ELEGANT_TEXT_HEIGHT_ENABLED = 0;
+    constexpr jint ELEGANT_TEXT_HEIGHT_DISABLED = 1;
+
+    static jint getElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         Paint* obj = reinterpret_cast<Paint*>(paintHandle);
-        return obj->getFamilyVariant() == minikin::FamilyVariant::ELEGANT;
+        const std::optional<minikin::FamilyVariant>& familyVariant = obj->getFamilyVariant();
+        if (familyVariant.has_value()) {
+            if (familyVariant.value() == minikin::FamilyVariant::ELEGANT) {
+                return ELEGANT_TEXT_HEIGHT_ENABLED;
+            } else {
+                return ELEGANT_TEXT_HEIGHT_DISABLED;
+            }
+        } else {
+            return ELEGANT_TEXT_HEIGHT_UNSET;
+        }
     }
 
-    static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean aa) {
+    static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint value) {
         Paint* obj = reinterpret_cast<Paint*>(paintHandle);
-        obj->setFamilyVariant(
-                aa ? minikin::FamilyVariant::ELEGANT : minikin::FamilyVariant::DEFAULT);
+        switch (value) {
+            case ELEGANT_TEXT_HEIGHT_ENABLED:
+                obj->setFamilyVariant(minikin::FamilyVariant::ELEGANT);
+                return;
+            case ELEGANT_TEXT_HEIGHT_DISABLED:
+                obj->setFamilyVariant(minikin::FamilyVariant::DEFAULT);
+                return;
+            case ELEGANT_TEXT_HEIGHT_UNSET:
+            default:
+                obj->resetFamilyVariant();
+                return;
+        }
     }
 
     static jfloat getTextSize(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
@@ -1178,8 +1202,8 @@
         {"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign},
         {"nSetTextLocalesByMinikinLocaleListId", "(JI)V",
          (void*)PaintGlue::setTextLocalesByMinikinLocaleListId},
-        {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight},
-        {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight},
+        {"nGetElegantTextHeight", "(J)I", (void*)PaintGlue::getElegantTextHeight},
+        {"nSetElegantTextHeight", "(JI)V", (void*)PaintGlue::setElegantTextHeight},
         {"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize},
         {"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize},
         {"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX},
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java
index af3c295..5a27435 100644
--- a/media/java/android/media/AudioDeviceAttributes.java
+++ b/media/java/android/media/AudioDeviceAttributes.java
@@ -68,7 +68,7 @@
     /**
      * The unique address of the device. Some devices don't have addresses, only an empty string.
      */
-    private final @NonNull String mAddress;
+    private @NonNull String mAddress;
     /**
      * The non-unique name of the device. Some devices don't have names, only an empty string.
      * Should not be used as a unique identifier for a device.
@@ -188,6 +188,21 @@
 
     /**
      * @hide
+     * Copy Constructor.
+     * @param ada the copied AudioDeviceAttributes
+     */
+    public AudioDeviceAttributes(AudioDeviceAttributes ada) {
+        mRole = ada.getRole();
+        mType = ada.getType();
+        mAddress = ada.getAddress();
+        mName = ada.getName();
+        mNativeType = ada.getInternalType();
+        mAudioProfiles = ada.getAudioProfiles();
+        mAudioDescriptors = ada.getAudioDescriptors();
+    }
+
+    /**
+     * @hide
      * Returns the role of a device
      * @return the role
      */
@@ -218,6 +233,15 @@
 
     /**
      * @hide
+     * Sets the device address. Only used by audio service.
+     */
+    public void setAddress(@NonNull String address) {
+        Objects.requireNonNull(address);
+        mAddress = address;
+    }
+
+    /**
+     * @hide
      * Returns the name of the audio device, or an empty string for devices without one
      * @return the device name
      */
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index c6e9c03..603e19f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -220,6 +220,7 @@
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE,
+        Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
         Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED,
         Settings.Secure.NOTIFICATION_BUBBLES,
         Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 0727677..5457c35 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -309,6 +309,9 @@
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+                BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE,
                 new InclusiveIntegerRangeValidator(
                         Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE_NONE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index f06b31c..7db189b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1850,6 +1850,10 @@
                 SecureSettingsProto.Accessibility
                         .ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED);
         dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+                SecureSettingsProto.Accessibility
+                        .ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED);
+        dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_GESTURE,
                 SecureSettingsProto.Accessibility
                         .ACCESSIBILITY_MAGNIFICATION_GESTURE);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 4e2fad0..95d7039 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -40,7 +40,6 @@
 import static com.android.providers.settings.SettingsState.isConfigSettingsKey;
 import static com.android.providers.settings.SettingsState.isGlobalSettingsKey;
 import static com.android.providers.settings.SettingsState.isSecureSettingsKey;
-import static com.android.providers.settings.SettingsState.isSsaidSettingsKey;
 import static com.android.providers.settings.SettingsState.isSystemSettingsKey;
 import static com.android.providers.settings.SettingsState.makeKey;
 
@@ -412,6 +411,9 @@
         SettingsState.cacheSystemPackageNamesAndSystemSignature(getContext());
         synchronized (mLock) {
             mSettingsRegistry.migrateAllLegacySettingsIfNeededLocked();
+            for (UserInfo user : mUserManager.getAliveUsers()) {
+                mSettingsRegistry.ensureSettingsForUserLocked(user.id);
+            }
             mSettingsRegistry.syncSsaidTableOnStartLocked();
         }
         mHandler.post(() -> {
@@ -427,65 +429,53 @@
     public Bundle call(String method, String name, Bundle args) {
         final int requestingUserId = getRequestingUserId(args);
         switch (method) {
-            case Settings.CALL_METHOD_GET_CONFIG: {
+            case Settings.CALL_METHOD_GET_CONFIG -> {
                 Setting setting = getConfigSetting(name);
                 return packageValueForCallResult(SETTINGS_TYPE_CONFIG, name, requestingUserId,
                         setting, isTrackingGeneration(args));
             }
-
-            case Settings.CALL_METHOD_GET_GLOBAL: {
+            case Settings.CALL_METHOD_GET_GLOBAL -> {
                 Setting setting = getGlobalSetting(name);
                 return packageValueForCallResult(SETTINGS_TYPE_GLOBAL, name, requestingUserId,
                         setting, isTrackingGeneration(args));
             }
-
-            case Settings.CALL_METHOD_GET_SECURE: {
+            case Settings.CALL_METHOD_GET_SECURE -> {
                 Setting setting = getSecureSetting(name, requestingUserId);
                 return packageValueForCallResult(SETTINGS_TYPE_SECURE, name, requestingUserId,
                         setting, isTrackingGeneration(args));
             }
-
-            case Settings.CALL_METHOD_GET_SYSTEM: {
+            case Settings.CALL_METHOD_GET_SYSTEM -> {
                 Setting setting = getSystemSetting(name, requestingUserId);
                 return packageValueForCallResult(SETTINGS_TYPE_SYSTEM, name, requestingUserId,
                         setting, isTrackingGeneration(args));
             }
-
-            case Settings.CALL_METHOD_PUT_CONFIG: {
+            case Settings.CALL_METHOD_PUT_CONFIG -> {
                 String value = getSettingValue(args);
                 final boolean makeDefault = getSettingMakeDefault(args);
                 insertConfigSetting(name, value, makeDefault);
-                break;
             }
-
-            case Settings.CALL_METHOD_PUT_GLOBAL: {
+            case Settings.CALL_METHOD_PUT_GLOBAL -> {
                 String value = getSettingValue(args);
                 String tag = getSettingTag(args);
                 final boolean makeDefault = getSettingMakeDefault(args);
                 final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
                 insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false,
                         overrideableByRestore);
-                break;
             }
-
-            case Settings.CALL_METHOD_PUT_SECURE: {
+            case Settings.CALL_METHOD_PUT_SECURE -> {
                 String value = getSettingValue(args);
                 String tag = getSettingTag(args);
                 final boolean makeDefault = getSettingMakeDefault(args);
                 final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
                 insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false,
                         overrideableByRestore);
-                break;
             }
-
-            case Settings.CALL_METHOD_PUT_SYSTEM: {
+            case Settings.CALL_METHOD_PUT_SYSTEM -> {
                 String value = getSettingValue(args);
                 boolean overrideableByRestore = getSettingOverrideableByRestore(args);
                 insertSystemSetting(name, value, requestingUserId, overrideableByRestore);
-                break;
             }
-
-            case Settings.CALL_METHOD_SET_ALL_CONFIG: {
+            case Settings.CALL_METHOD_SET_ALL_CONFIG -> {
                 String prefix = getSettingPrefix(args);
                 Map<String, String> flags = getSettingFlags(args);
                 Bundle result = new Bundle();
@@ -493,120 +483,96 @@
                         setAllConfigSettings(prefix, flags));
                 return result;
             }
-
-            case Settings.CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG: {
+            case Settings.CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG -> {
                 final int mode = getSyncDisabledMode(args);
                 setSyncDisabledModeConfig(mode);
-                break;
             }
-
-            case Settings.CALL_METHOD_GET_SYNC_DISABLED_MODE_CONFIG: {
+            case Settings.CALL_METHOD_GET_SYNC_DISABLED_MODE_CONFIG -> {
                 Bundle result = new Bundle();
                 result.putInt(Settings.KEY_CONFIG_GET_SYNC_DISABLED_MODE_RETURN,
                         getSyncDisabledModeConfig());
                 return result;
             }
-
-            case Settings.CALL_METHOD_RESET_CONFIG: {
+            case Settings.CALL_METHOD_RESET_CONFIG -> {
                 final int mode = getResetModeEnforcingPermission(args);
                 String prefix = getSettingPrefix(args);
                 resetConfigSetting(mode, prefix);
-                break;
             }
-
-            case Settings.CALL_METHOD_RESET_GLOBAL: {
+            case Settings.CALL_METHOD_RESET_GLOBAL -> {
                 final int mode = getResetModeEnforcingPermission(args);
                 String tag = getSettingTag(args);
                 resetGlobalSetting(requestingUserId, mode, tag);
-                break;
             }
-
-            case Settings.CALL_METHOD_RESET_SECURE: {
+            case Settings.CALL_METHOD_RESET_SECURE -> {
                 final int mode = getResetModeEnforcingPermission(args);
                 String tag = getSettingTag(args);
                 resetSecureSetting(requestingUserId, mode, tag);
-                break;
             }
-
-            case Settings.CALL_METHOD_RESET_SYSTEM: {
+            case Settings.CALL_METHOD_RESET_SYSTEM -> {
                 final int mode = getResetModeEnforcingPermission(args);
                 String tag = getSettingTag(args);
                 resetSystemSetting(requestingUserId, mode, tag);
-                break;
             }
-
-            case Settings.CALL_METHOD_DELETE_CONFIG: {
-                int rows  = deleteConfigSetting(name) ? 1 : 0;
+            case Settings.CALL_METHOD_DELETE_CONFIG -> {
+                int rows = deleteConfigSetting(name) ? 1 : 0;
                 Bundle result = new Bundle();
                 result.putInt(RESULT_ROWS_DELETED, rows);
                 return result;
             }
-
-            case Settings.CALL_METHOD_DELETE_GLOBAL: {
+            case Settings.CALL_METHOD_DELETE_GLOBAL -> {
                 int rows = deleteGlobalSetting(name, requestingUserId, false) ? 1 : 0;
                 Bundle result = new Bundle();
                 result.putInt(RESULT_ROWS_DELETED, rows);
                 return result;
             }
-
-            case Settings.CALL_METHOD_DELETE_SECURE: {
+            case Settings.CALL_METHOD_DELETE_SECURE -> {
                 int rows = deleteSecureSetting(name, requestingUserId, false) ? 1 : 0;
                 Bundle result = new Bundle();
                 result.putInt(RESULT_ROWS_DELETED, rows);
                 return result;
             }
-
-            case Settings.CALL_METHOD_DELETE_SYSTEM: {
+            case Settings.CALL_METHOD_DELETE_SYSTEM -> {
                 int rows = deleteSystemSetting(name, requestingUserId) ? 1 : 0;
                 Bundle result = new Bundle();
                 result.putInt(RESULT_ROWS_DELETED, rows);
                 return result;
             }
-
-            case Settings.CALL_METHOD_LIST_CONFIG: {
+            case Settings.CALL_METHOD_LIST_CONFIG -> {
                 String prefix = getSettingPrefix(args);
                 Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix),
                         isTrackingGeneration(args));
                 reportDeviceConfigAccess(prefix);
                 return result;
             }
-
-            case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG: {
+            case Settings.CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG -> {
                 RemoteCallback callback = args.getParcelable(
                         Settings.CALL_METHOD_MONITOR_CALLBACK_KEY);
                 setMonitorCallback(callback);
-                break;
             }
-
-            case Settings.CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG: {
+            case Settings.CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG -> {
                 clearMonitorCallback();
-                break;
             }
-
-            case Settings.CALL_METHOD_LIST_GLOBAL: {
+            case Settings.CALL_METHOD_LIST_GLOBAL -> {
                 Bundle result = new Bundle();
                 result.putStringArrayList(RESULT_SETTINGS_LIST,
                         buildSettingsList(getAllGlobalSettings(null)));
                 return result;
             }
-
-            case Settings.CALL_METHOD_LIST_SECURE: {
+            case Settings.CALL_METHOD_LIST_SECURE -> {
                 Bundle result = new Bundle();
                 result.putStringArrayList(RESULT_SETTINGS_LIST,
                         buildSettingsList(getAllSecureSettings(requestingUserId, null)));
                 return result;
             }
-
-            case Settings.CALL_METHOD_LIST_SYSTEM: {
+            case Settings.CALL_METHOD_LIST_SYSTEM -> {
                 Bundle result = new Bundle();
                 result.putStringArrayList(RESULT_SETTINGS_LIST,
                         buildSettingsList(getAllSystemSettings(requestingUserId, null)));
                 return result;
             }
-
-            default: {
+            default -> {
                 Slog.w(LOG_TAG, "call() with invalid method: " + method);
-            } break;
+            }
         }
 
         return null;
@@ -638,7 +604,7 @@
         }
 
         switch (args.table) {
-            case TABLE_GLOBAL: {
+            case TABLE_GLOBAL -> {
                 if (args.name != null) {
                     Setting setting = getGlobalSetting(args.name);
                     return packageSettingForQuery(setting, normalizedProjection);
@@ -646,8 +612,7 @@
                     return getAllGlobalSettings(projection);
                 }
             }
-
-            case TABLE_SECURE: {
+            case TABLE_SECURE -> {
                 final int userId = UserHandle.getCallingUserId();
                 if (args.name != null) {
                     Setting setting = getSecureSetting(args.name, userId);
@@ -656,8 +621,7 @@
                     return getAllSecureSettings(userId, projection);
                 }
             }
-
-            case TABLE_SYSTEM: {
+            case TABLE_SYSTEM -> {
                 final int userId = UserHandle.getCallingUserId();
                 if (args.name != null) {
                     Setting setting = getSystemSetting(args.name, userId);
@@ -666,8 +630,7 @@
                     return getAllSystemSettings(userId, projection);
                 }
             }
-
-            default: {
+            default -> {
                 throw new IllegalArgumentException("Invalid Uri path:" + uri);
             }
         }
@@ -708,30 +671,27 @@
         String value = values.getAsString(Settings.Secure.VALUE);
 
         switch (table) {
-            case TABLE_GLOBAL: {
+            case TABLE_GLOBAL -> {
                 if (insertGlobalSetting(name, value, null, false,
                         UserHandle.getCallingUserId(), false,
                         /* overrideableByRestore */ false)) {
-                    return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);
+                    return Uri.withAppendedPath(Global.CONTENT_URI, name);
                 }
-            } break;
-
-            case TABLE_SECURE: {
+            }
+            case TABLE_SECURE -> {
                 if (insertSecureSetting(name, value, null, false,
                         UserHandle.getCallingUserId(), false,
                         /* overrideableByRestore */ false)) {
-                    return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
+                    return Uri.withAppendedPath(Secure.CONTENT_URI, name);
                 }
-            } break;
-
-            case TABLE_SYSTEM: {
+            }
+            case TABLE_SYSTEM -> {
                 if (insertSystemSetting(name, value, UserHandle.getCallingUserId(),
                         /* overridableByRestore */ false)) {
                     return Uri.withAppendedPath(Settings.System.CONTENT_URI, name);
                 }
-            } break;
-
-            default: {
+            }
+            default -> {
                 throw new IllegalArgumentException("Bad Uri path:" + uri);
             }
         }
@@ -775,22 +735,19 @@
         }
 
         switch (args.table) {
-            case TABLE_GLOBAL: {
+            case TABLE_GLOBAL -> {
                 final int userId = UserHandle.getCallingUserId();
                 return deleteGlobalSetting(args.name, userId, false) ? 1 : 0;
             }
-
-            case TABLE_SECURE: {
+            case TABLE_SECURE -> {
                 final int userId = UserHandle.getCallingUserId();
                 return deleteSecureSetting(args.name, userId, false) ? 1 : 0;
             }
-
-            case TABLE_SYSTEM: {
+            case TABLE_SYSTEM -> {
                 final int userId = UserHandle.getCallingUserId();
                 return deleteSystemSetting(args.name, userId) ? 1 : 0;
             }
-
-            default: {
+            default -> {
                 throw new IllegalArgumentException("Bad Uri path:" + uri);
             }
         }
@@ -816,24 +773,21 @@
         String value = values.getAsString(Settings.Secure.VALUE);
 
         switch (args.table) {
-            case TABLE_GLOBAL: {
+            case TABLE_GLOBAL -> {
                 final int userId = UserHandle.getCallingUserId();
                 return updateGlobalSetting(args.name, value, null, false,
                         userId, false) ? 1 : 0;
             }
-
-            case TABLE_SECURE: {
+            case TABLE_SECURE -> {
                 final int userId = UserHandle.getCallingUserId();
                 return updateSecureSetting(args.name, value, null, false,
                         userId, false) ? 1 : 0;
             }
-
-            case TABLE_SYSTEM: {
+            case TABLE_SYSTEM -> {
                 final int userId = UserHandle.getCallingUserId();
                 return updateSystemSetting(args.name, value, userId) ? 1 : 0;
             }
-
-            default: {
+            default -> {
                 throw new IllegalArgumentException("Invalid Uri path:" + uri);
             }
         }
@@ -1034,27 +988,38 @@
 
     private void registerBroadcastReceivers() {
         IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_ADDED);
         userFilter.addAction(Intent.ACTION_USER_REMOVED);
         userFilter.addAction(Intent.ACTION_USER_STOPPED);
 
         getContext().registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
+                if (intent.getAction() == null) {
+                    return;
+                }
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
-                        UserHandle.USER_SYSTEM);
+                        UserHandle.USER_NULL);
+                if (userId == UserHandle.USER_NULL) {
+                    return;
+                }
 
                 switch (intent.getAction()) {
-                    case Intent.ACTION_USER_REMOVED: {
+                    case Intent.ACTION_USER_ADDED -> {
+                        synchronized (mLock) {
+                            mSettingsRegistry.ensureSettingsForUserLocked(userId);
+                        }
+                    }
+                    case Intent.ACTION_USER_REMOVED -> {
                         synchronized (mLock) {
                             mSettingsRegistry.removeUserStateLocked(userId, true);
                         }
-                    } break;
-
-                    case Intent.ACTION_USER_STOPPED: {
+                    }
+                    case Intent.ACTION_USER_STOPPED -> {
                         synchronized (mLock) {
                             mSettingsRegistry.removeUserStateLocked(userId, false);
                         }
-                    } break;
+                    }
                 }
             }
         }, userFilter);
@@ -1350,26 +1315,24 @@
         // Perform the mutation.
         synchronized (mLock) {
             switch (operation) {
-                case MUTATION_OPERATION_INSERT: {
+                case MUTATION_OPERATION_INSERT -> {
                     enforceDeviceConfigWritePermission(getContext(), Collections.singleton(name));
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG,
                             UserHandle.USER_SYSTEM, name, value, null, makeDefault, true,
                             callingPackage, false, null,
                             /* overrideableByRestore */ false);
                 }
-
-                case MUTATION_OPERATION_DELETE: {
+                case MUTATION_OPERATION_DELETE -> {
                     enforceDeviceConfigWritePermission(getContext(), Collections.singleton(name));
                     return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_CONFIG,
                             UserHandle.USER_SYSTEM, name, false, null);
                 }
-
-                case MUTATION_OPERATION_RESET: {
+                case MUTATION_OPERATION_RESET -> {
                     enforceDeviceConfigWritePermission(getContext(),
                             getAllConfigFlags(prefix).keySet());
-                    mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG,
+                    return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_CONFIG,
                             UserHandle.USER_SYSTEM, callingPackage, mode, null, prefix);
-                } return true;
+                }
             }
         }
 
@@ -1523,7 +1486,7 @@
         enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId);
 
         // If this is a setting that is currently restricted for this user, do not allow
         // unrestricting changes.
@@ -1536,28 +1499,25 @@
         // Perform the mutation.
         synchronized (mLock) {
             switch (operation) {
-                case MUTATION_OPERATION_INSERT: {
+                case MUTATION_OPERATION_INSERT -> {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
                             callingPackage, forceNotify,
                             CRITICAL_GLOBAL_SETTINGS, overrideableByRestore);
                 }
-
-                case MUTATION_OPERATION_DELETE: {
+                case MUTATION_OPERATION_DELETE -> {
                     return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, name, forceNotify, CRITICAL_GLOBAL_SETTINGS);
                 }
-
-                case MUTATION_OPERATION_UPDATE: {
+                case MUTATION_OPERATION_UPDATE -> {
                     return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
                             callingPackage, forceNotify, CRITICAL_GLOBAL_SETTINGS);
                 }
-
-                case MUTATION_OPERATION_RESET: {
-                    mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_GLOBAL,
+                case MUTATION_OPERATION_RESET -> {
+                    return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_GLOBAL,
                             UserHandle.USER_SYSTEM, callingPackage, mode, tag);
-                } return true;
+                }
             }
         }
 
@@ -1580,12 +1540,12 @@
         }
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(userId);
 
         // The relevant "calling package" userId will be the owning userId for some
         // profiles, and we can't do the lookup inside our [lock held] loop, so work out
         // up front who the effective "new SSAID" user ID for that settings name will be.
-        final int ssaidUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId,
+        final int ssaidUserId = resolveOwningUserIdForSecureSetting(callingUserId,
                 Settings.Secure.ANDROID_ID);
         final PackageInfo ssaidCallingPkg = getCallingPackageInfo(ssaidUserId);
 
@@ -1600,7 +1560,7 @@
             for (int i = 0; i < nameCount; i++) {
                 String name = names.get(i);
                 // Determine the owning user as some profile settings are cloned from the parent.
-                final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId,
+                final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId,
                         name);
 
                 if (!isSecureSettingAccessible(name)) {
@@ -1638,13 +1598,13 @@
         }
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId);
 
         // Ensure the caller can access the setting.
         enforceSettingReadable(name, SETTINGS_TYPE_SECURE, UserHandle.getCallingUserId());
 
         // Determine the owning user as some profile settings are cloned from the parent.
-        final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
+        final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId, name);
 
         if (!isSecureSettingAccessible(name)) {
             // This caller is not permitted to access this setting. Pretend the setting doesn't
@@ -1811,7 +1771,7 @@
         enforceHasAtLeastOnePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId);
 
         // If this is a setting that is currently restricted for this user, do not allow
         // unrestricting changes.
@@ -1820,7 +1780,7 @@
         }
 
         // Determine the owning user as some profile settings are cloned from the parent.
-        final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
+        final int owningUserId = resolveOwningUserIdForSecureSetting(callingUserId, name);
 
         // Only the owning user can change the setting.
         if (owningUserId != callingUserId) {
@@ -1832,28 +1792,25 @@
         // Mutate the value.
         synchronized (mLock) {
             switch (operation) {
-                case MUTATION_OPERATION_INSERT: {
+                case MUTATION_OPERATION_INSERT -> {
                     return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
                             owningUserId, name, value, tag, makeDefault,
                             callingPackage, forceNotify, CRITICAL_SECURE_SETTINGS,
                             overrideableByRestore);
                 }
-
-                case MUTATION_OPERATION_DELETE: {
+                case MUTATION_OPERATION_DELETE -> {
                     return mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SECURE,
                             owningUserId, name, forceNotify, CRITICAL_SECURE_SETTINGS);
                 }
-
-                case MUTATION_OPERATION_UPDATE: {
+                case MUTATION_OPERATION_UPDATE -> {
                     return mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SECURE,
                             owningUserId, name, value, tag, makeDefault,
                             callingPackage, forceNotify, CRITICAL_SECURE_SETTINGS);
                 }
-
-                case MUTATION_OPERATION_RESET: {
-                    mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE,
+                case MUTATION_OPERATION_RESET -> {
+                    return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE,
                             UserHandle.USER_SYSTEM, callingPackage, mode, tag);
-                } return true;
+                }
             }
         }
 
@@ -1866,7 +1823,7 @@
         }
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(userId);
 
         synchronized (mLock) {
             List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SYSTEM, callingUserId);
@@ -1903,7 +1860,7 @@
         }
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(requestingUserId);
 
         // Ensure the caller can access the setting.
         enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, UserHandle.getCallingUserId());
@@ -1978,7 +1935,7 @@
         }
 
         // Resolve the userId on whose behalf the call is made.
-        final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(runAsUserId);
+        final int callingUserId = resolveCallingUserIdEnforcingPermissions(runAsUserId);
 
         if (isSettingRestrictedForUser(name, callingUserId, value, Binder.getCallingUid())) {
             Slog.e(LOG_TAG, "UserId: " + callingUserId + " is disallowed to change system "
@@ -2012,37 +1969,30 @@
         // Mutate the value.
         synchronized (mLock) {
             switch (operation) {
-                case MUTATION_OPERATION_INSERT: {
+                case MUTATION_OPERATION_INSERT -> {
                     validateSystemSettingValue(name, value);
                     success = mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
                             owningUserId, name, value, null, false, callingPackage,
                             false, null, overrideableByRestore);
-                    break;
                 }
-
-                case MUTATION_OPERATION_DELETE: {
+                case MUTATION_OPERATION_DELETE -> {
                     success = mSettingsRegistry.deleteSettingLocked(SETTINGS_TYPE_SYSTEM,
                             owningUserId, name, false, null);
-                    break;
                 }
-
-                case MUTATION_OPERATION_UPDATE: {
+                case MUTATION_OPERATION_UPDATE -> {
                     validateSystemSettingValue(name, value);
                     success = mSettingsRegistry.updateSettingLocked(SETTINGS_TYPE_SYSTEM,
                             owningUserId, name, value, null, false, callingPackage,
                             false, null);
-                    break;
                 }
-
-                case MUTATION_OPERATION_RESET: {
+                case MUTATION_OPERATION_RESET -> {
                     success = mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SYSTEM,
                             runAsUserId, callingPackage, mode, tag);
-                    break;
                 }
-
-                default:
+                default -> {
                     success = false;
                     Slog.e(LOG_TAG, "Unknown operation code: " + operation);
+                }
             }
         }
 
@@ -2113,8 +2063,8 @@
      * Returns {@code true} if the specified secure setting should be accessible to the caller.
      */
     private boolean isSecureSettingAccessible(String name) {
-        switch (name) {
-            case "bluetooth_address":
+        return switch (name) {
+            case "bluetooth_address" ->
                 // BluetoothManagerService for some reason stores the Android's Bluetooth MAC
                 // address in this secure setting. Secure settings can normally be read by any app,
                 // which thus enables them to bypass the recently introduced restrictions on access
@@ -2122,22 +2072,23 @@
                 // To mitigate this we make this setting available only to callers privileged to see
                 // this device's MAC addresses, same as through public API
                 // BluetoothAdapter.getAddress() (see BluetoothManagerService for details).
-                return getContext().checkCallingOrSelfPermission(
-                        Manifest.permission.LOCAL_MAC_ADDRESS) == PackageManager.PERMISSION_GRANTED;
-            default:
-                return true;
-        }
+                    getContext().checkCallingOrSelfPermission(Manifest.permission.LOCAL_MAC_ADDRESS)
+                            == PackageManager.PERMISSION_GRANTED;
+            default -> true;
+        };
     }
 
-    private int resolveOwningUserIdForSecureSettingLocked(int userId, String setting) {
-        return resolveOwningUserIdLocked(userId, sSecureCloneToManagedSettings, setting);
+    private int resolveOwningUserIdForSecureSetting(int userId, String setting) {
+        // no need to lock because sSecureCloneToManagedSettings is never modified
+        return resolveOwningUserId(userId, sSecureCloneToManagedSettings, setting);
     }
 
+    @GuardedBy("mLock")
     private int resolveOwningUserIdForSystemSettingLocked(int userId, String setting) {
         final int parentId;
         // Resolves dependency if setting has a dependency and the calling user has a parent
         if (sSystemCloneFromParentOnDependency.containsKey(setting)
-                && (parentId = getGroupParentLocked(userId)) != userId) {
+                && (parentId = getGroupParent(userId)) != userId) {
             // The setting has a dependency and the profile has a parent
             String dependency = sSystemCloneFromParentOnDependency.get(setting);
             // Lookup the dependency setting as ourselves, some callers may not have access to it.
@@ -2151,11 +2102,11 @@
                 Binder.restoreCallingIdentity(token);
             }
         }
-        return resolveOwningUserIdLocked(userId, sSystemCloneToManagedSettings, setting);
+        return resolveOwningUserId(userId, sSystemCloneToManagedSettings, setting);
     }
 
-    private int resolveOwningUserIdLocked(int userId, Set<String> keys, String name) {
-        final int parentId = getGroupParentLocked(userId);
+    private int resolveOwningUserId(int userId, Set<String> keys, String name) {
+        final int parentId = getGroupParent(userId);
         if (parentId != userId && keys.contains(name)) {
             return parentId;
         }
@@ -2174,9 +2125,8 @@
         }
 
         switch (operation) {
-            case MUTATION_OPERATION_INSERT:
-                // Insert updates.
-            case MUTATION_OPERATION_UPDATE: {
+            // Insert updates.
+            case MUTATION_OPERATION_INSERT, MUTATION_OPERATION_UPDATE -> {
                 if (Settings.System.PUBLIC_SETTINGS.contains(name)) {
                     return;
                 }
@@ -2192,9 +2142,8 @@
 
                 warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
                         packageInfo.applicationInfo.targetSdkVersion, name);
-            } break;
-
-            case MUTATION_OPERATION_DELETE: {
+            }
+            case MUTATION_OPERATION_DELETE -> {
                 if (Settings.System.PUBLIC_SETTINGS.contains(name)
                         || Settings.System.PRIVATE_SETTINGS.contains(name)) {
                     throw new IllegalArgumentException("You cannot delete system defined"
@@ -2212,34 +2161,26 @@
 
                 warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
                         packageInfo.applicationInfo.targetSdkVersion, name);
-            } break;
+            }
         }
     }
 
-    private Set<String> getInstantAppAccessibleSettings(int settingsType) {
-        switch (settingsType) {
-            case SETTINGS_TYPE_GLOBAL:
-                return Settings.Global.INSTANT_APP_SETTINGS;
-            case SETTINGS_TYPE_SECURE:
-                return Settings.Secure.INSTANT_APP_SETTINGS;
-            case SETTINGS_TYPE_SYSTEM:
-                return Settings.System.INSTANT_APP_SETTINGS;
-            default:
-                throw new IllegalArgumentException("Invalid settings type: " + settingsType);
-        }
+    private static Set<String> getInstantAppAccessibleSettings(int settingsType) {
+        return switch (settingsType) {
+            case SETTINGS_TYPE_GLOBAL -> Global.INSTANT_APP_SETTINGS;
+            case SETTINGS_TYPE_SECURE -> Secure.INSTANT_APP_SETTINGS;
+            case SETTINGS_TYPE_SYSTEM -> Settings.System.INSTANT_APP_SETTINGS;
+            default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType);
+        };
     }
 
-    private Set<String> getOverlayInstantAppAccessibleSettings(int settingsType) {
-        switch (settingsType) {
-            case SETTINGS_TYPE_GLOBAL:
-                return OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS;
-            case SETTINGS_TYPE_SYSTEM:
-                return OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS;
-            case SETTINGS_TYPE_SECURE:
-                return OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS;
-            default:
-                throw new IllegalArgumentException("Invalid settings type: " + settingsType);
-        }
+    private static Set<String> getOverlayInstantAppAccessibleSettings(int settingsType) {
+        return switch (settingsType) {
+            case SETTINGS_TYPE_GLOBAL -> OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS;
+            case SETTINGS_TYPE_SYSTEM -> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS;
+            case SETTINGS_TYPE_SECURE -> OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS;
+            default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType);
+        };
     }
 
     @GuardedBy("mLock")
@@ -2270,7 +2211,7 @@
         switch (settingName) {
             // missing READ_PRIVILEGED_PHONE_STATE permission protection
             // see alternative API {@link SubscriptionManager#getPreferredDataSubscriptionId()
-            case Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION:
+            case Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION -> {
                 // app-compat handling, not break apps targeting on previous SDKs.
                 if (CompatChanges.isChangeEnabled(
                         ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL)) {
@@ -2278,7 +2219,7 @@
                             Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                             "access global settings MULTI_SIM_DATA_CALL_SUBSCRIPTION");
                 }
-                break;
+            }
         }
         if (!ai.isInstantApp()) {
             return;
@@ -2306,23 +2247,22 @@
         final Set<String> readableFields;
         final ArrayMap<String, Integer> readableFieldsWithMaxTargetSdk;
         switch (settingsType) {
-            case SETTINGS_TYPE_GLOBAL:
+            case SETTINGS_TYPE_GLOBAL -> {
                 allFields = sAllGlobalSettings;
                 readableFields = sReadableGlobalSettings;
                 readableFieldsWithMaxTargetSdk = sReadableGlobalSettingsWithMaxTargetSdk;
-                break;
-            case SETTINGS_TYPE_SYSTEM:
+            }
+            case SETTINGS_TYPE_SYSTEM -> {
                 allFields = sAllSystemSettings;
                 readableFields = sReadableSystemSettings;
                 readableFieldsWithMaxTargetSdk = sReadableSystemSettingsWithMaxTargetSdk;
-                break;
-            case SETTINGS_TYPE_SECURE:
+            }
+            case SETTINGS_TYPE_SECURE -> {
                 allFields = sAllSecureSettings;
                 readableFields = sReadableSecureSettings;
                 readableFieldsWithMaxTargetSdk = sReadableSecureSettingsWithMaxTargetSdk;
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid settings type: " + settingsType);
+            }
+            default -> throw new IllegalArgumentException("Invalid settings type: " + settingsType);
         }
 
         if (allFields.contains(settingName)) {
@@ -2380,7 +2320,7 @@
         throw new IllegalStateException("Calling package doesn't exist");
     }
 
-    private int getGroupParentLocked(int userId) {
+    private int getGroupParent(int userId) {
         // Most frequent use case.
         if (userId == UserHandle.USER_SYSTEM) {
             return userId;
@@ -2480,7 +2420,7 @@
         }
     }
 
-    private static int resolveCallingUserIdEnforcingPermissionsLocked(int requestingUserId) {
+    private static int resolveCallingUserIdEnforcingPermissions(int requestingUserId) {
         if (requestingUserId == UserHandle.getCallingUserId()) {
             return requestingUserId;
         }
@@ -2654,28 +2594,28 @@
     private static int getResetModeEnforcingPermission(Bundle args) {
         final int mode = (args != null) ? args.getInt(Settings.CALL_METHOD_RESET_MODE_KEY) : 0;
         switch (mode) {
-            case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: {
+            case Settings.RESET_MODE_UNTRUSTED_DEFAULTS -> {
                 if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) {
                     throw new SecurityException("Only system, shell/root on a "
                             + "debuggable build can reset to untrusted defaults");
                 }
                 return mode;
             }
-            case Settings.RESET_MODE_UNTRUSTED_CHANGES: {
+            case Settings.RESET_MODE_UNTRUSTED_CHANGES -> {
                 if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) {
                     throw new SecurityException("Only system, shell/root on a "
                             + "debuggable build can reset untrusted changes");
                 }
                 return mode;
             }
-            case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
+            case Settings.RESET_MODE_TRUSTED_DEFAULTS -> {
                 if (!isCallerSystemOrShellOrRootOnDebuggableBuild()) {
                     throw new SecurityException("Only system, shell/root on a "
                             + "debuggable build can reset to trusted defaults");
                 }
                 return mode;
             }
-            case Settings.RESET_MODE_PACKAGE_DEFAULTS: {
+            case Settings.RESET_MODE_PACKAGE_DEFAULTS -> {
                 return mode;
             }
         }
@@ -2736,21 +2676,18 @@
             String column = cursor.getColumnName(i);
 
             switch (column) {
-                case Settings.NameValueTable._ID: {
+                case Settings.NameValueTable._ID -> {
                     values[i] = setting.getId();
-                } break;
-
-                case Settings.NameValueTable.NAME: {
+                }
+                case Settings.NameValueTable.NAME -> {
                     values[i] = setting.getName();
-                } break;
-
-                case Settings.NameValueTable.VALUE: {
+                }
+                case Settings.NameValueTable.VALUE -> {
                     values[i] = setting.getValue();
-                } break;
-
-                case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE: {
+                }
+                case Settings.NameValueTable.IS_PRESERVED_IN_RESTORE -> {
                     values[i] = String.valueOf(setting.isValuePreservedInRestore());
-                } break;
+                }
             }
         }
 
@@ -2762,19 +2699,11 @@
     }
 
     private String resolveCallingPackage() {
-        switch (Binder.getCallingUid()) {
-            case Process.ROOT_UID: {
-                return "root";
-            }
-
-            case Process.SHELL_UID: {
-                return "com.android.shell";
-            }
-
-            default: {
-                return getCallingPackage();
-            }
-        }
+        return switch (Binder.getCallingUid()) {
+            case Process.ROOT_UID -> "root";
+            case Process.SHELL_UID -> "com.android.shell";
+            default -> getCallingPackage();
+        };
     }
 
     private static final class Arguments {
@@ -2796,17 +2725,17 @@
         public Arguments(Uri uri, String where, String[] whereArgs, boolean supportAll) {
             final int segmentSize = uri.getPathSegments().size();
             switch (segmentSize) {
-                case 1: {
+                case 1 -> {
                     if (where != null
                             && (WHERE_PATTERN_WITH_PARAM_NO_BRACKETS.matcher(where).matches()
-                                || WHERE_PATTERN_WITH_PARAM_IN_BRACKETS.matcher(where).matches())
+                            || WHERE_PATTERN_WITH_PARAM_IN_BRACKETS.matcher(where).matches())
                             && whereArgs.length == 1) {
                         name = whereArgs[0];
                         table = computeTableForSetting(uri, name);
                         return;
                     } else if (where != null
                             && (WHERE_PATTERN_NO_PARAM_NO_BRACKETS.matcher(where).matches()
-                                || WHERE_PATTERN_NO_PARAM_IN_BRACKETS.matcher(where).matches())) {
+                            || WHERE_PATTERN_NO_PARAM_IN_BRACKETS.matcher(where).matches())) {
                         final int startIndex = Math.max(where.indexOf("'"),
                                 where.indexOf("\"")) + 1;
                         final int endIndex = Math.max(where.lastIndexOf("'"),
@@ -2819,15 +2748,14 @@
                         table = computeTableForSetting(uri, null);
                         return;
                     }
-                } break;
-
-                case 2: {
+                }
+                case 2 -> {
                     if (where == null && whereArgs == null) {
                         name = uri.getPathSegments().get(1);
                         table = computeTableForSetting(uri, name);
                         return;
                     }
-                } break;
+                }
             }
 
             EventLogTags.writeUnsupportedSettingsQuery(
@@ -2960,6 +2888,7 @@
             mBackupManager = new BackupManager(getContext());
         }
 
+        @GuardedBy("mLock")
         private void generateUserKeyLocked(int userId) {
             // Generate a random key for each user used for creating a new ssaid.
             final byte[] keyBytes = new byte[32];
@@ -2983,6 +2912,7 @@
             return ByteBuffer.allocate(4).putInt(data.length).array();
         }
 
+        @GuardedBy("mLock")
         public Setting generateSsaidLocked(PackageInfo callingPkg, int userId) {
             // Read the user's key from the ssaid table.
             Setting userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY);
@@ -3044,6 +2974,7 @@
             return getSettingLocked(SETTINGS_TYPE_SSAID, userId, uid);
         }
 
+        @GuardedBy("mLock")
         private void syncSsaidTableOnStartLocked() {
             // Verify that each user's packages and ssaid's are in sync.
             for (UserInfo user : mUserManager.getAliveUsers()) {
@@ -3078,15 +3009,17 @@
             }
         }
 
+        @GuardedBy("mLock")
         public List<String> getSettingsNamesLocked(int type, int userId) {
             final int key = makeKey(type, userId);
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState == null) {
                 return new ArrayList<>();
             }
             return settingsState.getSettingNamesLocked();
         }
 
+        @GuardedBy("mLock")
         public SparseBooleanArray getKnownUsersLocked() {
             SparseBooleanArray users = new SparseBooleanArray();
             for (int i = mSettingsStates.size()-1; i >= 0; i--) {
@@ -3095,17 +3028,19 @@
             return users;
         }
 
+        @GuardedBy("mLock")
         @Nullable
         public SettingsState getSettingsLocked(int type, int userId) {
             final int key = makeKey(type, userId);
-            return peekSettingsStateLocked(key);
+            return mSettingsStates.get(key);
         }
 
-        public boolean ensureSettingsForUserLocked(int userId) {
+        @GuardedBy("mLock")
+        public void ensureSettingsForUserLocked(int userId) {
             // First make sure this user actually exists.
             if (mUserManager.getUserInfo(userId) == null) {
                 Slog.wtf(LOG_TAG, "Requested user " + userId + " does not exist");
-                return false;
+                return;
             }
 
             // Migrate the setting for this user if needed.
@@ -3143,9 +3078,9 @@
             // Upgrade the settings to the latest version.
             UpgradeController upgrader = new UpgradeController(userId);
             upgrader.upgradeIfNeededLocked();
-            return true;
         }
 
+        @GuardedBy("mLock")
         private void ensureSettingsStateLocked(int key) {
             if (mSettingsStates.get(key) == null) {
                 final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));
@@ -3155,6 +3090,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         public void removeUserStateLocked(int userId, boolean permanently) {
             // We always keep the global settings in memory.
 
@@ -3166,12 +3102,7 @@
                     mSettingsStates.remove(systemKey);
                     systemSettingsState.destroyLocked(null);
                 } else {
-                    systemSettingsState.destroyLocked(new Runnable() {
-                        @Override
-                        public void run() {
-                            mSettingsStates.remove(systemKey);
-                        }
-                    });
+                    systemSettingsState.destroyLocked(() -> mSettingsStates.remove(systemKey));
                 }
             }
 
@@ -3183,12 +3114,7 @@
                     mSettingsStates.remove(secureKey);
                     secureSettingsState.destroyLocked(null);
                 } else {
-                    secureSettingsState.destroyLocked(new Runnable() {
-                        @Override
-                        public void run() {
-                            mSettingsStates.remove(secureKey);
-                        }
-                    });
+                    secureSettingsState.destroyLocked(() -> mSettingsStates.remove(secureKey));
                 }
             }
 
@@ -3200,12 +3126,7 @@
                     mSettingsStates.remove(ssaidKey);
                     ssaidSettingsState.destroyLocked(null);
                 } else {
-                    ssaidSettingsState.destroyLocked(new Runnable() {
-                        @Override
-                        public void run() {
-                            mSettingsStates.remove(ssaidKey);
-                        }
-                    });
+                    ssaidSettingsState.destroyLocked(() -> mSettingsStates.remove(ssaidKey));
                 }
             }
 
@@ -3213,6 +3134,7 @@
             mGenerationRegistry.onUserRemoved(userId);
         }
 
+        @GuardedBy("mLock")
         public boolean insertSettingLocked(int type, int userId, String name, String value,
                 String tag, boolean makeDefault, String packageName, boolean forceNotify,
                 Set<String> criticalSettings, boolean overrideableByRestore) {
@@ -3220,6 +3142,7 @@
                     packageName, forceNotify, criticalSettings, overrideableByRestore);
         }
 
+        @GuardedBy("mLock")
         public boolean insertSettingLocked(int type, int userId, String name, String value,
                 String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName,
                 boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) {
@@ -3232,7 +3155,7 @@
 
             boolean success = false;
             boolean wasUnsetNonPredefinedSetting = false;
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState != null) {
                 if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) {
                     wasUnsetNonPredefinedSetting = true;
@@ -3264,9 +3187,10 @@
          * Set Config Settings using consumed keyValues, returns true if the keyValues can be set,
          * false otherwise.
          */
+        @GuardedBy("mLock")
         public boolean setConfigSettingsLocked(int key, String prefix,
                 Map<String, String> keyValues, String packageName) {
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState != null) {
                 if (settingsState.isNewConfigBannedLocked(prefix, keyValues)) {
                     return false;
@@ -3283,12 +3207,13 @@
             return true;
         }
 
+        @GuardedBy("mLock")
         public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify,
                 Set<String> criticalSettings) {
             final int key = makeKey(type, userId);
 
             boolean success = false;
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState != null) {
                 success = settingsState.deleteSettingLocked(name);
             }
@@ -3306,13 +3231,14 @@
             return success;
         }
 
+        @GuardedBy("mLock")
         public boolean updateSettingLocked(int type, int userId, String name, String value,
                 String tag, boolean makeDefault, String packageName, boolean forceNotify,
                 Set<String> criticalSettings) {
             final int key = makeKey(type, userId);
 
             boolean success = false;
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState != null) {
                 success = settingsState.updateSettingLocked(name, value, tag,
                         makeDefault, packageName);
@@ -3331,10 +3257,11 @@
             return success;
         }
 
+        @GuardedBy("mLock")
         public Setting getSettingLocked(int type, int userId, String name) {
             final int key = makeKey(type, userId);
 
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState == null) {
                 return null;
             }
@@ -3352,16 +3279,18 @@
             return Global.SECURE_FRP_MODE.equals(setting.getName());
         }
 
+        @GuardedBy("mLock")
         public boolean resetSettingsLocked(int type, int userId, String packageName, int mode,
                 String tag) {
             return resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/
                     null);
         }
 
+        @GuardedBy("mLock")
         public boolean resetSettingsLocked(int type, int userId, String packageName, int mode,
                 String tag, @Nullable String prefix) {
             final int key = makeKey(type, userId);
-            SettingsState settingsState = peekSettingsStateLocked(key);
+            SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState == null) {
                 return false;
             }
@@ -3369,7 +3298,7 @@
             boolean success = false;
             banConfigurationIfNecessary(type, prefix, settingsState);
             switch (mode) {
-                case Settings.RESET_MODE_PACKAGE_DEFAULTS: {
+                case Settings.RESET_MODE_PACKAGE_DEFAULTS -> {
                     for (String name : settingsState.getSettingNamesLocked()) {
                         boolean someSettingChanged = false;
                         Setting setting = settingsState.getSettingLocked(name);
@@ -3389,9 +3318,8 @@
                             success = true;
                         }
                     }
-                } break;
-
-                case Settings.RESET_MODE_UNTRUSTED_DEFAULTS: {
+                }
+                case Settings.RESET_MODE_UNTRUSTED_DEFAULTS -> {
                     for (String name : settingsState.getSettingNamesLocked()) {
                         boolean someSettingChanged = false;
                         Setting setting = settingsState.getSettingLocked(name);
@@ -3411,9 +3339,8 @@
                             success = true;
                         }
                     }
-                } break;
-
-                case Settings.RESET_MODE_UNTRUSTED_CHANGES: {
+                }
+                case Settings.RESET_MODE_UNTRUSTED_CHANGES -> {
                     for (String name : settingsState.getSettingNamesLocked()) {
                         boolean someSettingChanged = false;
                         Setting setting = settingsState.getSettingLocked(name);
@@ -3439,9 +3366,8 @@
                             success = true;
                         }
                     }
-                } break;
-
-                case Settings.RESET_MODE_TRUSTED_DEFAULTS: {
+                }
+                case Settings.RESET_MODE_TRUSTED_DEFAULTS -> {
                     for (String name : settingsState.getSettingNamesLocked()) {
                         Setting setting = settingsState.getSettingLocked(name);
                         boolean someSettingChanged = false;
@@ -3464,11 +3390,12 @@
                             success = true;
                         }
                     }
-                } break;
+                }
             }
             return success;
         }
 
+        @GuardedBy("mLock")
         public void removeSettingsForPackageLocked(String packageName, int userId) {
             // Global and secure settings are signature protected. Apps signed
             // by the platform certificate are generally not uninstalled  and
@@ -3482,6 +3409,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         public void onUidRemovedLocked(int uid) {
             final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID,
                     UserHandle.getUserId(uid));
@@ -3490,19 +3418,7 @@
             }
         }
 
-        @Nullable
-        private SettingsState peekSettingsStateLocked(int key) {
-            SettingsState settingsState = mSettingsStates.get(key);
-            if (settingsState != null) {
-                return settingsState;
-            }
-
-            if (!ensureSettingsForUserLocked(getUserIdFromKey(key))) {
-                return null;
-            }
-            return mSettingsStates.get(key);
-        }
-
+        @GuardedBy("mLock")
         private void migrateAllLegacySettingsIfNeededLocked() {
             final int key = makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
             File globalFile = getSettingsFile(key);
@@ -3538,6 +3454,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         private void migrateLegacySettingsForUserIfNeededLocked(int userId) {
             // Every user has secure settings and if no file we need to migrate.
             final int secureKey = makeKey(SETTINGS_TYPE_SECURE, userId);
@@ -3552,6 +3469,7 @@
             migrateLegacySettingsForUserLocked(dbHelper, database, userId);
         }
 
+        @GuardedBy("mLock")
         private void migrateLegacySettingsForUserLocked(DatabaseHelper dbHelper,
                 SQLiteDatabase database, int userId) {
             // Move over the system settings.
@@ -3596,6 +3514,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         private void migrateLegacySettingsLocked(SettingsState settingsState,
                 SQLiteDatabase database, String table) {
             SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
@@ -3630,7 +3549,7 @@
             }
         }
 
-        @GuardedBy("secureSettings.mLock")
+        @GuardedBy("mLock")
         private void ensureSecureSettingAndroidIdSetLocked(SettingsState secureSettings) {
             Setting value = secureSettings.getSettingLocked(Settings.Secure.ANDROID_ID);
 
@@ -3706,6 +3625,7 @@
                     name, type, changeType);
         }
 
+        @GuardedBy("mLock")
         private void notifyForConfigSettingsChangeLocked(int key, String prefix,
                 List<String> changedSettings) {
 
@@ -3787,30 +3707,18 @@
             }
         }
 
-        private File getSettingsFile(int key) {
-            if (isConfigSettingsKey(key)) {
-                final int userId = getUserIdFromKey(key);
-                return new File(Environment.getUserSystemDirectory(userId),
-                        SETTINGS_FILE_CONFIG);
-            } else if (isGlobalSettingsKey(key)) {
-                final int userId = getUserIdFromKey(key);
-                return new File(Environment.getUserSystemDirectory(userId),
-                        SETTINGS_FILE_GLOBAL);
-            } else if (isSystemSettingsKey(key)) {
-                final int userId = getUserIdFromKey(key);
-                return new File(Environment.getUserSystemDirectory(userId),
-                        SETTINGS_FILE_SYSTEM);
-            } else if (isSecureSettingsKey(key)) {
-                final int userId = getUserIdFromKey(key);
-                return new File(Environment.getUserSystemDirectory(userId),
-                        SETTINGS_FILE_SECURE);
-            } else if (isSsaidSettingsKey(key)) {
-                final int userId = getUserIdFromKey(key);
-                return new File(Environment.getUserSystemDirectory(userId),
-                        SETTINGS_FILE_SSAID);
-            } else {
-                throw new IllegalArgumentException("Invalid settings key:" + key);
-            }
+        private static File getSettingsFile(int key) {
+            final int userId = getUserIdFromKey(key);
+            final int type = getTypeFromKey(key);
+            final File userSystemDirectory = Environment.getUserSystemDirectory(userId);
+            return switch (type) {
+                case SETTINGS_TYPE_CONFIG -> new File(userSystemDirectory, SETTINGS_FILE_CONFIG);
+                case SETTINGS_TYPE_GLOBAL -> new File(userSystemDirectory, SETTINGS_FILE_GLOBAL);
+                case SETTINGS_TYPE_SYSTEM -> new File(userSystemDirectory, SETTINGS_FILE_SYSTEM);
+                case SETTINGS_TYPE_SECURE -> new File(userSystemDirectory, SETTINGS_FILE_SECURE);
+                case SETTINGS_TYPE_SSAID -> new File(userSystemDirectory, SETTINGS_FILE_SSAID);
+                default -> throw new IllegalArgumentException("Invalid settings key:" + key);
+            };
         }
 
         private Uri getNotificationUriFor(int key, String name) {
@@ -3833,14 +3741,11 @@
 
         private int getMaxBytesPerPackageForType(int type) {
             switch (type) {
-                case SETTINGS_TYPE_CONFIG:
-                case SETTINGS_TYPE_GLOBAL:
-                case SETTINGS_TYPE_SECURE:
-                case SETTINGS_TYPE_SSAID: {
+                case SETTINGS_TYPE_CONFIG, SETTINGS_TYPE_GLOBAL, SETTINGS_TYPE_SECURE,
+                        SETTINGS_TYPE_SSAID -> {
                     return SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED;
                 }
-
-                default: {
+                default -> {
                     return SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED;
                 }
             }
@@ -3857,7 +3762,7 @@
             @Override
             public void handleMessage(Message msg) {
                 switch (msg.what) {
-                    case MSG_NOTIFY_URI_CHANGED: {
+                    case MSG_NOTIFY_URI_CHANGED -> {
                         final int userId = msg.arg1;
                         Uri uri = (Uri) msg.obj;
                         try {
@@ -3868,12 +3773,11 @@
                         if (DEBUG) {
                             Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri);
                         }
-                    } break;
-
-                    case MSG_NOTIFY_DATA_CHANGED: {
+                    }
+                    case MSG_NOTIFY_DATA_CHANGED -> {
                         mBackupManager.dataChanged();
                         scheduleWriteFallbackFilesJob();
-                    } break;
+                    }
                 }
             }
         }
@@ -3887,6 +3791,7 @@
                 mUserId = userId;
             }
 
+            @GuardedBy("mLock")
             public void upgradeIfNeededLocked() {
                 // The version of all settings for a user is the same (all users have secure).
                 SettingsState secureSettings = getSettingsLocked(
@@ -3944,18 +3849,22 @@
                 systemSettings.setVersionLocked(newVersion);
             }
 
+            @GuardedBy("mLock")
             private SettingsState getGlobalSettingsLocked() {
                 return getSettingsLocked(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
             }
 
+            @GuardedBy("mLock")
             private SettingsState getSecureSettingsLocked(int userId) {
                 return getSettingsLocked(SETTINGS_TYPE_SECURE, userId);
             }
 
+            @GuardedBy("mLock")
             private SettingsState getSsaidSettingsLocked(int userId) {
                 return getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
             }
 
+            @GuardedBy("mLock")
             private SettingsState getSystemSettingsLocked(int userId) {
                 return getSettingsLocked(SETTINGS_TYPE_SYSTEM, userId);
             }
@@ -5399,7 +5308,7 @@
                     // next version step.
                     // If this is a new profile, check if a secure setting exists for the
                     // owner of the profile and use that value for the work profile.
-                    int owningId = resolveOwningUserIdForSecureSettingLocked(userId,
+                    int owningId = resolveOwningUserIdForSecureSetting(userId,
                             NOTIFICATION_BUBBLES);
                     Setting previous = getGlobalSettingsLocked()
                             .getSettingLocked("notification_bubbles");
@@ -6068,18 +5977,22 @@
                 return currentVersion;
             }
 
+            @GuardedBy("mLock")
             private void initGlobalSettingsDefaultValLocked(String key, boolean val) {
                 initGlobalSettingsDefaultValLocked(key, val ? "1" : "0");
             }
 
+            @GuardedBy("mLock")
             private void initGlobalSettingsDefaultValLocked(String key, int val) {
                 initGlobalSettingsDefaultValLocked(key, String.valueOf(val));
             }
 
+            @GuardedBy("mLock")
             private void initGlobalSettingsDefaultValLocked(String key, long val) {
                 initGlobalSettingsDefaultValLocked(key, String.valueOf(val));
             }
 
+            @GuardedBy("mLock")
             private void initGlobalSettingsDefaultValLocked(String key, String val) {
                 final SettingsState globalSettings = getGlobalSettingsLocked();
                 Setting currentSetting = globalSettings.getSettingLocked(key);
@@ -6198,6 +6111,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         private void ensureLegacyDefaultValueAndSystemSetUpdatedLocked(SettingsState settings,
                 int userId) {
             List<String> names = settings.getSettingNamesLocked();
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
index 2b33057..2984cd3 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/InstallNonMarketAppsDeprecationTest.java
@@ -17,10 +17,12 @@
 package com.android.providers.settings;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assume.assumeTrue;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Process;
@@ -42,6 +44,8 @@
 import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
 
 @LargeTest
 public class InstallNonMarketAppsDeprecationTest extends BaseSettingsProviderTest {
@@ -54,35 +58,42 @@
     private boolean mSystemSetUserRestriction;
     private List<Integer> mUsersAddedByTest;
 
-    private String waitTillValueChanges(String errorMessage, String oldValue) {
+    private static String waitTillValueChanges(String errorMessage, @Nullable String oldValue,
+                                        Supplier<String> action) {
         boolean interrupted = false;
         final long startTime = SystemClock.uptimeMillis();
-        String newValue = getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS);
-        while (newValue.equals(oldValue) && SystemClock.uptimeMillis() <= (startTime
+        String newValue = action.get();
+        while (Objects.equals(newValue, oldValue) && SystemClock.uptimeMillis() <= (startTime
                 + USER_RESTRICTION_CHANGE_TIMEOUT)) {
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException exc) {
                 interrupted = true;
             }
-            newValue = getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS);
+            newValue = action.get();
         }
         if (interrupted) {
             Thread.currentThread().interrupt();
         }
-        assertFalse(errorMessage, oldValue.equals(newValue));
+        assertNotEquals(errorMessage, newValue, oldValue);
         return newValue;
     }
 
-    private String getSecureSettingForUserViaShell(int userId) throws IOException {
+    private String getSecureSettingForUserViaShell(int userId) {
         StringBuilder sb = new StringBuilder("settings get --user ");
         sb.append(userId + " secure ");
         sb.append(Settings.Secure.INSTALL_NON_MARKET_APPS);
         BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(
                 InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
                         sb.toString()).getFileDescriptor())));
-        String line = reader.readLine();
-        return line.trim();
+        String line;
+        try {
+            line = reader.readLine();
+        } catch (IOException e) {
+            return null;
+        }
+        final String result = line.trim();
+        return (result.isEmpty() || result.equals("null")) ? null : result;
     }
 
     @Override
@@ -121,7 +132,10 @@
 
         UserInfo newUser = mUm.createUser("TEST_USER", 0);
         mUsersAddedByTest.add(newUser.id);
-        String value = getSecureSettingForUserViaShell(newUser.id);
+        // wait till SettingsProvider reacts to the USER_ADDED event
+        String value = waitTillValueChanges(
+                "install_non_market_apps should be set for the new user", null,
+                () -> getSecureSettingForUserViaShell(newUser.id));
         assertEquals("install_non_market_apps should be 1 for a new user", value, "1");
     }
 
@@ -140,13 +154,15 @@
         mUm.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, !mHasUserRestriction);
         value = waitTillValueChanges(
                 "Changing user restriction did not change the value of install_non_market_apps",
-                value);
+                value,
+                () -> getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS));
         assertTrue("Invalid value", value.equals("1") || value.equals("0"));
 
         mUm.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, mHasUserRestriction);
         value = waitTillValueChanges(
                 "Changing user restriction did not change the value of install_non_market_apps",
-                value);
+                value,
+                () -> getSetting(SETTING_TYPE_SECURE, Settings.Secure.INSTALL_NON_MARKET_APPS));
         assertTrue("Invalid value", value.equals("1") || value.equals("0"));
     }
 
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 8f329b3..34545cf 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -36,6 +36,7 @@
 hwwang@google.com
 hyunyoungs@google.com
 ikateryna@google.com
+iyz@google.com
 jaggies@google.com
 jamesoleary@google.com
 jbolinger@google.com
@@ -54,10 +55,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
@@ -94,6 +97,7 @@
 tsuji@google.com
 twickham@google.com
 vadimt@google.com
+valiiftime@google.com
 vanjan@google.com
 victortulias@google.com
 winsonc@google.com
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
index df87d19d..91fe33c 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
@@ -30,6 +30,8 @@
          *
          * Currently treats the first supported size as the size to be rendered in, ignoring other
          * supported sizes.
+         *
+         * Cards are ordered by priority, from highest to lowest.
          */
         fun distributeCardsIntoColumns(
             cards: List<CommunalGridLayoutCard>,
@@ -37,7 +39,8 @@
             val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>()
 
             var capacityOfLastColumn = 0
-            for (card in cards) {
+            val sorted = cards.sortedByDescending { it.priority }
+            for (card in sorted) {
                 val cardSize = card.supportedSizes.first()
                 if (capacityOfLastColumn >= cardSize.value) {
                     // Card fits in last column
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
index c1974ca..50b7c5f 100644
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
+++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
@@ -41,7 +41,7 @@
                 ),
             )
 
-        assertDistribution(cards, expected)
+        assertDistributionBySize(cards, expected)
     }
 
     @Test
@@ -71,10 +71,30 @@
                 ),
             )
 
-        assertDistribution(cards, expected)
+        assertDistributionBySize(cards, expected)
     }
 
-    private fun assertDistribution(
+    @Test
+    fun distribution_sortByPriority() {
+        val cards =
+            listOf(
+                generateCard(priority = 2),
+                generateCard(priority = 7),
+                generateCard(priority = 10),
+                generateCard(priority = 1),
+                generateCard(priority = 5),
+            )
+        val expected =
+            listOf(
+                listOf(10, 7),
+                listOf(5, 2),
+                listOf(1),
+            )
+
+        assertDistributionByPriority(cards, expected)
+    }
+
+    private fun assertDistributionBySize(
         cards: List<CommunalGridLayoutCard>,
         expected: List<List<CommunalGridLayoutCard.Size>>,
     ) {
@@ -87,6 +107,19 @@
         }
     }
 
+    private fun assertDistributionByPriority(
+        cards: List<CommunalGridLayoutCard>,
+        expected: List<List<Int>>,
+    ) {
+        val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
+
+        for (c in expected.indices) {
+            for (r in expected[c].indices) {
+                assertThat(result[c][r].card.priority).isEqualTo(expected[c][r])
+            }
+        }
+    }
+
     private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
         return object : CommunalGridLayoutCard() {
             override val supportedSizes = listOf(size)
@@ -97,4 +130,16 @@
             }
         }
     }
+
+    private fun generateCard(priority: Int): CommunalGridLayoutCard {
+        return object : CommunalGridLayoutCard() {
+            override val supportedSizes = listOf(Size.HALF)
+            override val priority = priority
+
+            @Composable
+            override fun Content(modifier: Modifier, size: SizeF) {
+                Card(modifier = modifier, content = {})
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 689a0a2..eb71490 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -46,7 +46,6 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
-import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.layout
@@ -69,7 +68,6 @@
 import com.android.compose.modifiers.background
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.compose.theme.colorAttr
-import com.android.systemui.res.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
@@ -78,6 +76,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
 /** The Quick Settings footer actions row. */
@@ -89,8 +88,7 @@
 ) {
     val context = LocalContext.current
 
-    // Collect visibility and alphas as soon as we are composed, even when not visible.
-    val isVisible by viewModel.isVisible.collectAsState()
+    // Collect alphas as soon as we are composed, even when not visible.
     val alpha by viewModel.alpha.collectAsState()
     val backgroundAlpha = viewModel.backgroundAlpha.collectAsState()
 
@@ -142,11 +140,6 @@
         modifier
             .fillMaxWidth()
             .graphicsLayer { this.alpha = alpha }
-            .drawWithContent {
-                if (isVisible) {
-                    drawContent()
-                }
-            }
             .then(backgroundModifier)
             .padding(
                 top = dimensionResource(R.dimen.qs_footer_actions_top_padding),
@@ -328,7 +321,7 @@
         shape = CircleShape,
         color = colorAttr(R.attr.underSurface),
         contentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
-        borderStroke = BorderStroke(1.dp, colorAttr(R.attr.onShadeActive)),
+        borderStroke = BorderStroke(1.dp, colorAttr(R.attr.shadeInactive)),
         modifier = modifier.padding(horizontal = 4.dp),
         onClick = onClick,
     ) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 07ffd11..1620088 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -1,15 +1,17 @@
 /*
  * Copyright (C) 2017 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
+ * 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.
+ * 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.systemui.statusbar.phone;
@@ -19,8 +21,6 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -29,9 +29,9 @@
 import android.content.BroadcastReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -43,12 +43,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 @SmallTest
 public class SystemUIDialogTest extends SysuiTestCase {
@@ -76,12 +77,13 @@
 
         dialog.show();
         verify(mBroadcastDispatcher).registerReceiver(broadcastReceiverCaptor.capture(),
-                intentFilterCaptor.capture(), eq(null), any());
+                intentFilterCaptor.capture(), ArgumentMatchers.eq(null), ArgumentMatchers.any());
         assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_SCREEN_OFF));
         assertTrue(intentFilterCaptor.getValue().hasAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
 
         dialog.dismiss();
-        verify(mBroadcastDispatcher).unregisterReceiver(eq(broadcastReceiverCaptor.getValue()));
+        verify(mBroadcastDispatcher).unregisterReceiver(
+                ArgumentMatchers.eq(broadcastReceiverCaptor.getValue()));
     }
 
 
@@ -90,11 +92,12 @@
         final SystemUIDialog dialog = new SystemUIDialog(mContext, 0, false);
 
         dialog.show();
-        verify(mBroadcastDispatcher, never()).registerReceiver(any(), any(), eq(null), any());
+        verify(mBroadcastDispatcher, never()).registerReceiver(ArgumentMatchers.any(),
+                ArgumentMatchers.any(), ArgumentMatchers.eq(null), ArgumentMatchers.any());
         assertTrue(dialog.isShowing());
 
         dialog.dismiss();
-        verify(mBroadcastDispatcher, never()).unregisterReceiver(any());
+        verify(mBroadcastDispatcher, never()).unregisterReceiver(ArgumentMatchers.any());
         assertFalse(dialog.isShowing());
     }
 
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 05f4334..74d435d 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -248,4 +248,14 @@
 
     <!-- Tag set on the Compose implementation of the QS footer actions. -->
     <item type="id" name="tag_compose_qs_footer_actions" />
+
+    <!--
+    Ids for the device entry icon.
+        device_entry_icon_view: parent view of both device_entry_icon and device_entry_icon_bg
+        device_entry_icon_fg: fp/lock/unlock icon
+        device_entry_icon_bg: background protection behind the icon
+    -->
+    <item type="id" name="device_entry_icon_view" />
+    <item type="id" name="device_entry_icon_fg" />
+    <item type="id" name="device_entry_icon_bg" />
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 2e21255..3d8aaaf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -65,15 +65,23 @@
     private boolean mPaused;
 
     private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> {
-        // Check if this was the result of hitting the enter key
+        // Check if this was the result of hitting the IME done action
         final boolean isSoftImeEvent = event == null
                 && (actionId == EditorInfo.IME_NULL
                 || actionId == EditorInfo.IME_ACTION_DONE
                 || actionId == EditorInfo.IME_ACTION_NEXT);
-        final boolean isKeyboardEnterKey = event != null
-                && KeyEvent.isConfirmKey(event.getKeyCode())
-                && event.getAction() == KeyEvent.ACTION_DOWN;
-        if (isSoftImeEvent || isKeyboardEnterKey) {
+        if (isSoftImeEvent) {
+            verifyPasswordAndUnlock();
+            return true;
+        }
+        return false;
+    };
+
+    private final View.OnKeyListener mKeyListener = (v, keyCode, keyEvent) -> {
+        final boolean isKeyboardEnterKey = keyEvent != null
+                && KeyEvent.isConfirmKey(keyCode)
+                && keyEvent.getAction() == KeyEvent.ACTION_UP;
+        if (isKeyboardEnterKey) {
             verifyPasswordAndUnlock();
             return true;
         }
@@ -140,15 +148,16 @@
                 | InputType.TYPE_TEXT_VARIATION_PASSWORD);
 
         mView.onDevicePostureChanged(mPostureController.getDevicePosture());
+
         mPostureController.addCallback(mPostureCallback);
 
         // Set selected property on so the view can send accessibility events.
         mPasswordEntry.setSelected(true);
         mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
+        mPasswordEntry.setOnKeyListener(mKeyListener);
         mPasswordEntry.addTextChangedListener(mTextWatcher);
         // Poke the wakelock any time the text is selected or modified
         mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity());
-
         mSwitchImeButton.setOnClickListener(v -> {
             mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer
             // Do not show auxiliary subtypes in password lock screen.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 681aa70..175544d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -89,10 +89,7 @@
 
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (KeyEvent.isConfirmKey(keyCode)) {
-            mOkButton.performClick();
-            return true;
-        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+        if (keyCode == KeyEvent.KEYCODE_DEL) {
             mDeleteButton.performClick();
             return true;
         }
@@ -110,6 +107,15 @@
     }
 
     @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (KeyEvent.isConfirmKey(keyCode)) {
+            mOkButton.performClick();
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
     protected int getPromptReasonStringRes(int reason) {
         switch (reason) {
             case PROMPT_REASON_RESTART:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index b7d1171..376933d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -41,6 +41,9 @@
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
             return mView.onKeyDown(keyCode, event);
         }
+        if (event.getAction() == KeyEvent.ACTION_UP) {
+            return mView.onKeyUp(keyCode, event);
+        }
         return false;
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 272e0f2..934f9f9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -46,7 +46,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
-import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
@@ -240,15 +239,14 @@
             }
             REASON_AUTH_KEYGUARD -> {
                 if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
-                    udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds)
-                    UdfpsKeyguardViewController(
-                        view.addUdfpsView(R.layout.udfps_keyguard_view),
-                        statusBarStateController,
-                        primaryBouncerInteractor,
-                        dialogManager,
-                        dumpManager,
-                        alternateBouncerInteractor,
-                        udfpsKeyguardViewModels.get(),
+                    // note: empty controller, currently shows no visual affordance
+                    // instead SysUI will show the fingerprint icon in its DeviceEntryIconView
+                    UdfpsBpViewController(
+                            view.addUdfpsView(R.layout.udfps_bp_view),
+                            statusBarStateController,
+                            primaryBouncerInteractor,
+                            dialogManager,
+                            dumpManager
                     )
                 } else {
                     UdfpsKeyguardViewControllerLegacy(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 119ade4..c56dfde 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -72,7 +73,7 @@
     private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
     private val context: Context,
     private val keyguardIndicationController: KeyguardIndicationController,
-    private val lockIconViewController: LockIconViewController,
+    private val lockIconViewController: Lazy<LockIconViewController>,
     private val shadeInteractor: ShadeInteractor,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
@@ -131,7 +132,9 @@
         val indicationArea = KeyguardIndicationArea(context, null)
         keyguardIndicationController.setIndicationArea(indicationArea)
 
-        lockIconViewController.setLockIconView(LockIconView(context, null))
+        if (!featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+            lockIconViewController.get().setLockIconView(LockIconView(context, null))
+        }
     }
 
     private fun bindKeyguardRootView() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index 122c4c4..55b420b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -29,8 +29,10 @@
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 /** Handles key events arriving when the keyguard is showing or device is dozing. */
+@ExperimentalCoroutinesApi
 @SysUISingleton
 class KeyguardKeyEventInteractor
 @Inject
@@ -53,9 +55,14 @@
         }
 
         if (event.handleAction()) {
+            if (KeyEvent.isConfirmKey(event.keyCode)) {
+                if (isDeviceAwake()) {
+                    return collapseShadeLockedOrShowPrimaryBouncer()
+                }
+            }
+
             when (event.keyCode) {
                 KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
-                KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent()
             }
         }
         return false
@@ -91,16 +98,22 @@
                 (statusBarStateController.state != StatusBarState.SHADE) &&
                 statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
         if (shouldUnlockOnMenuPressed) {
-            shadeController.animateCollapseShadeForced()
-            return true
+            return collapseShadeLockedOrShowPrimaryBouncer()
         }
         return false
     }
 
-    private fun dispatchSpaceEvent(): Boolean {
-        if (isDeviceAwake() && statusBarStateController.state != StatusBarState.SHADE) {
-            shadeController.animateCollapseShadeForced()
-            return true
+    private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean {
+        when (statusBarStateController.state) {
+            StatusBarState.SHADE -> return false
+            StatusBarState.SHADE_LOCKED -> {
+                shadeController.animateCollapseShadeForced()
+                return true
+            }
+            StatusBarState.KEYGUARD -> {
+                statusBarKeyguardViewManager.showPrimaryBouncer(true)
+                return true
+            }
         }
         return false
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
new file mode 100644
index 0000000..e82ea7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.systemui.keyguard.ui.binder
+
+import android.annotation.SuppressLint
+import android.content.res.ColorStateList
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.common.ui.view.LongPressHandlingView
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object DeviceEntryIconViewBinder {
+
+    /**
+     * Updates UI for the device entry icon view (lock, unlock and fingerprint icons) and its
+     * background.
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    @JvmStatic
+    fun bind(
+        view: DeviceEntryIconView,
+        viewModel: DeviceEntryIconViewModel,
+        falsingManager: FalsingManager,
+    ) {
+        val iconView = view.iconView
+        val bgView = view.bgView
+        val longPressHandlingView = view.longPressHandlingView
+        longPressHandlingView.listener =
+            object : LongPressHandlingView.Listener {
+                override fun onLongPressDetected(view: View, x: Int, y: Int) {
+                    if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+                        return
+                    }
+                    viewModel.onLongPress()
+                }
+            }
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.iconViewModel.collect { iconViewModel ->
+                        iconView.setImageState(
+                            view.getIconState(iconViewModel.type, iconViewModel.useAodVariant),
+                            /* merge */ false
+                        )
+                        iconView.imageTintList = ColorStateList.valueOf(iconViewModel.tint)
+                        iconView.alpha = iconViewModel.alpha
+                        iconView.setPadding(
+                            iconViewModel.padding,
+                            iconViewModel.padding,
+                            iconViewModel.padding,
+                            iconViewModel.padding,
+                        )
+                    }
+                }
+                launch {
+                    viewModel.backgroundViewModel.collect { bgViewModel ->
+                        bgView.alpha = bgViewModel.alpha
+                        bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint)
+                    }
+                }
+                launch {
+                    viewModel.burnInViewModel.collect { burnInViewModel ->
+                        view.translationX = burnInViewModel.x.toFloat()
+                        view.translationY = burnInViewModel.y.toFloat()
+                        view.aodFpDrawable.progress = burnInViewModel.progress
+                    }
+                }
+                launch {
+                    viewModel.isLongPressEnabled.collect { isEnabled ->
+                        longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
+                    }
+                }
+                launch {
+                    viewModel.accessibilityDelegateHint.collect { hint ->
+                        view.accessibilityHintType = hint
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
new file mode 100644
index 0000000..c9e3954
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt
@@ -0,0 +1,272 @@
+/*
+ * 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.systemui.keyguard.ui.view
+
+import android.content.Context
+import android.graphics.drawable.AnimatedStateListDrawable
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.util.AttributeSet
+import android.util.StateSet
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.FrameLayout
+import android.widget.ImageView
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import com.airbnb.lottie.LottieCompositionFactory
+import com.airbnb.lottie.LottieDrawable
+import com.android.systemui.common.ui.view.LongPressHandlingView
+import com.android.systemui.res.R
+
+class DeviceEntryIconView
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet?,
+    defStyleAttrs: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttrs) {
+    val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs)
+    val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg }
+    val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg }
+    val aodFpDrawable: LottieDrawable = LottieDrawable()
+    var accessibilityHintType: AccessibilityHintType = AccessibilityHintType.NONE
+
+    private var animatedIconDrawable: AnimatedStateListDrawable = AnimatedStateListDrawable()
+
+    init {
+        setupIconStates()
+        setupIconTransitions()
+        setupAccessibilityDelegate()
+
+        // Ordering matters. From background to foreground we want:
+        //     bgView, iconView, longpressHandlingView overlay
+        addBgImageView()
+        addIconImageView()
+        addLongpressHandlingView()
+    }
+
+    private fun setupAccessibilityDelegate() {
+        accessibilityDelegate =
+            object : AccessibilityDelegate() {
+                private val accessibilityAuthenticateHint =
+                    AccessibilityNodeInfo.AccessibilityAction(
+                        AccessibilityNodeInfoCompat.ACTION_CLICK,
+                        resources.getString(R.string.accessibility_authenticate_hint)
+                    )
+                private val accessibilityEnterHint =
+                    AccessibilityNodeInfo.AccessibilityAction(
+                        AccessibilityNodeInfoCompat.ACTION_CLICK,
+                        resources.getString(R.string.accessibility_enter_hint)
+                    )
+                override fun onInitializeAccessibilityNodeInfo(
+                    v: View,
+                    info: AccessibilityNodeInfo
+                ) {
+                    super.onInitializeAccessibilityNodeInfo(v, info)
+                    when (accessibilityHintType) {
+                        AccessibilityHintType.AUTHENTICATE ->
+                            info.addAction(accessibilityAuthenticateHint)
+                        AccessibilityHintType.ENTER -> info.addAction(accessibilityEnterHint)
+                        AccessibilityHintType.NONE -> return
+                    }
+                }
+            }
+    }
+
+    private fun setupIconStates() {
+        // Lockscreen States
+        // LOCK
+        animatedIconDrawable.addState(
+            getIconState(IconType.LOCK, false),
+            context.getDrawable(R.drawable.ic_lock)!!,
+            R.id.locked,
+        )
+        // UNLOCK
+        animatedIconDrawable.addState(
+            getIconState(IconType.UNLOCK, false),
+            context.getDrawable(R.drawable.ic_unlocked)!!,
+            R.id.unlocked,
+        )
+        // FINGERPRINT
+        animatedIconDrawable.addState(
+            getIconState(IconType.FINGERPRINT, false),
+            context.getDrawable(R.drawable.ic_kg_fingerprint)!!,
+            R.id.locked_fp,
+        )
+
+        // AOD states
+        // LOCK
+        animatedIconDrawable.addState(
+            getIconState(IconType.LOCK, true),
+            context.getDrawable(R.drawable.ic_lock_aod)!!,
+            R.id.locked_aod,
+        )
+        // UNLOCK
+        animatedIconDrawable.addState(
+            getIconState(IconType.UNLOCK, true),
+            context.getDrawable(R.drawable.ic_unlocked_aod)!!,
+            R.id.unlocked_aod,
+        )
+        // FINGERPRINT
+        LottieCompositionFactory.fromRawRes(mContext, R.raw.udfps_aod_fp).addListener { result ->
+            aodFpDrawable.setComposition(result)
+        }
+        animatedIconDrawable.addState(
+            getIconState(IconType.FINGERPRINT, true),
+            aodFpDrawable,
+            R.id.udfps_aod_fp,
+        )
+
+        // WILDCARD: should always be the last state added since any states will match with this
+        // and therefore won't get matched with subsequent states.
+        animatedIconDrawable.addState(
+            StateSet.WILD_CARD,
+            context.getDrawable(R.color.transparent)!!,
+            R.id.no_icon,
+        )
+    }
+
+    private fun setupIconTransitions() {
+        // LockscreenFp <=> LockscreenUnlocked
+        animatedIconDrawable.addTransition(
+            R.id.locked_fp,
+            R.id.unlocked,
+            context.getDrawable(R.drawable.fp_to_unlock) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+        animatedIconDrawable.addTransition(
+            R.id.unlocked,
+            R.id.locked_fp,
+            context.getDrawable(R.drawable.unlock_to_fp) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+
+        // LockscreenLocked <=> AodLocked
+        animatedIconDrawable.addTransition(
+            R.id.locked_aod,
+            R.id.locked,
+            context.getDrawable(R.drawable.lock_aod_to_ls) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+        animatedIconDrawable.addTransition(
+            R.id.locked,
+            R.id.locked_aod,
+            context.getDrawable(R.drawable.lock_ls_to_aod) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+
+        // LockscreenUnlocked <=> AodUnlocked
+        animatedIconDrawable.addTransition(
+            R.id.unlocked_aod,
+            R.id.unlocked,
+            context.getDrawable(R.drawable.unlocked_aod_to_ls) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+        animatedIconDrawable.addTransition(
+            R.id.unlocked,
+            R.id.unlocked_aod,
+            context.getDrawable(R.drawable.unlocked_ls_to_aod) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+
+        // LockscreenLocked <=> LockscreenUnlocked
+        animatedIconDrawable.addTransition(
+            R.id.locked,
+            R.id.unlocked,
+            context.getDrawable(R.drawable.lock_to_unlock) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+        animatedIconDrawable.addTransition(
+            R.id.unlocked,
+            R.id.locked,
+            context.getDrawable(R.drawable.unlocked_to_locked) as AnimatedVectorDrawable,
+            /* reversible */ false,
+        )
+
+        // LockscreenFingerprint <=> LockscreenLocked
+        animatedIconDrawable.addTransition(
+            R.id.locked_fp,
+            R.id.locked,
+            context.getDrawable(R.drawable.fp_to_locked) as AnimatedVectorDrawable,
+            /* reversible */ true,
+        )
+
+        // LockscreenUnlocked <=> AodLocked
+        animatedIconDrawable.addTransition(
+            R.id.unlocked,
+            R.id.locked_aod,
+            context.getDrawable(R.drawable.unlocked_to_aod_lock) as AnimatedVectorDrawable,
+            /* reversible */ true,
+        )
+    }
+
+    private fun addLongpressHandlingView() {
+        addView(longPressHandlingView)
+        val lp = longPressHandlingView.layoutParams as LayoutParams
+        lp.height = ViewGroup.LayoutParams.MATCH_PARENT
+        lp.width = ViewGroup.LayoutParams.MATCH_PARENT
+        longPressHandlingView.setLayoutParams(lp)
+    }
+
+    private fun addIconImageView() {
+        iconView.scaleType = ImageView.ScaleType.CENTER_CROP
+        iconView.setImageDrawable(animatedIconDrawable)
+        addView(iconView)
+        val lp = iconView.layoutParams as LayoutParams
+        lp.height = ViewGroup.LayoutParams.MATCH_PARENT
+        lp.width = ViewGroup.LayoutParams.MATCH_PARENT
+        lp.gravity = Gravity.CENTER
+        iconView.setLayoutParams(lp)
+    }
+
+    private fun addBgImageView() {
+        bgView.setImageDrawable(context.getDrawable(R.drawable.fingerprint_bg))
+        addView(bgView)
+        val lp = bgView.layoutParams as LayoutParams
+        lp.height = ViewGroup.LayoutParams.MATCH_PARENT
+        lp.width = ViewGroup.LayoutParams.MATCH_PARENT
+        bgView.setLayoutParams(lp)
+    }
+
+    fun getIconState(icon: IconType, aod: Boolean): IntArray {
+        val lockIconState = IntArray(2)
+        when (icon) {
+            IconType.LOCK -> lockIconState[0] = android.R.attr.state_first
+            IconType.UNLOCK -> lockIconState[0] = android.R.attr.state_last
+            IconType.FINGERPRINT -> lockIconState[0] = android.R.attr.state_middle
+        }
+        if (aod) {
+            lockIconState[1] = android.R.attr.state_single
+        } else {
+            lockIconState[1] = -android.R.attr.state_single
+        }
+        return lockIconState
+    }
+
+    enum class IconType {
+        LOCK,
+        UNLOCK,
+        FINGERPRINT,
+    }
+
+    enum class AccessibilityHintType {
+        NONE,
+        AUTHENTICATE,
+        ENTER,
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index d8e4396..21eba56 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -23,8 +23,8 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
@@ -44,7 +44,7 @@
 @Inject
 constructor(
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
-    defaultLockIconSection: DefaultLockIconSection,
+    defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
     defaultShortcutsSection: DefaultShortcutsSection,
     defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
@@ -61,7 +61,7 @@
     override val sections =
         setOf(
             defaultIndicationAreaSection,
-            defaultLockIconSection,
+            defaultDeviceEntryIconSection,
             defaultShortcutsSection,
             defaultAmbientIndicationAreaSection,
             defaultSettingsPopupMenuSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index ce76f56..f8dd7c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -23,8 +23,8 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
@@ -38,7 +38,7 @@
 @Inject
 constructor(
     defaultIndicationAreaSection: DefaultIndicationAreaSection,
-    defaultLockIconSection: DefaultLockIconSection,
+    defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
     defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
     alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
@@ -54,7 +54,7 @@
     override val sections =
         setOf(
             defaultIndicationAreaSection,
-            defaultLockIconSection,
+            defaultDeviceEntryIconSection,
             defaultAmbientIndicationAreaSection,
             defaultSettingsPopupMenuSection,
             alignShortcutsToUdfpsSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
index f4bc713..62c5988 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt
@@ -33,11 +33,18 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
+import dagger.Lazy
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-class DefaultLockIconSection
+@ExperimentalCoroutinesApi
+class DefaultDeviceEntryIconSection
 @Inject
 constructor(
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -46,24 +53,47 @@
     private val context: Context,
     private val notificationPanelView: NotificationPanelView,
     private val featureFlags: FeatureFlags,
-    private val lockIconViewController: LockIconViewController,
+    private val lockIconViewController: Lazy<LockIconViewController>,
+    private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
+    private val falsingManager: Lazy<FalsingManager>,
 ) : KeyguardSection() {
-    private val lockIconViewId = R.id.lock_icon_view
+    private val deviceEntryIconViewId = R.id.device_entry_icon_view
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (!featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+        if (
+            !featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON) &&
+                !featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)
+        ) {
             return
         }
-        notificationPanelView.findViewById<View>(lockIconViewId).let {
+
+        notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
             notificationPanelView.removeView(it)
         }
-        val view = LockIconView(context, null).apply { id = lockIconViewId }
+
+        val view =
+            if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+                DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId }
+            } else {
+                // Flags.MIGRATE_LOCK_ICON
+                LockIconView(context, null).apply { id = deviceEntryIconViewId }
+            }
         constraintLayout.addView(view)
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        constraintLayout.findViewById<LockIconView?>(lockIconViewId)?.let {
-            lockIconViewController.setLockIconView(it)
+        if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+            constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let {
+                DeviceEntryIconViewBinder.bind(
+                    it,
+                    deviceEntryIconViewModel.get(),
+                    falsingManager.get(),
+                )
+            }
+        } else {
+            constraintLayout.findViewById<LockIconView?>(deviceEntryIconViewId)?.let {
+                lockIconViewController.get().setLockIconView(it)
+            }
         }
     }
 
@@ -84,30 +114,30 @@
         val defaultDensity =
             DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
                 DisplayMetrics.DENSITY_DEFAULT.toFloat()
-        val lockIconRadiusPx = (defaultDensity * 36).toInt()
+        val iconRadiusPx = (defaultDensity * 36).toInt()
 
         if (isUdfpsSupported) {
             authController.udfpsLocation?.let { udfpsLocation ->
-                centerLockIcon(udfpsLocation, authController.udfpsRadius, constraintSet)
+                centerIcon(udfpsLocation, authController.udfpsRadius, constraintSet)
             }
         } else {
-            centerLockIcon(
+            centerIcon(
                 Point(
                     (widthPixels / 2).toInt(),
-                    (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
+                    (heightPixels - ((mBottomPaddingPx + iconRadiusPx) * scaleFactor)).toInt()
                 ),
-                lockIconRadiusPx * scaleFactor,
+                iconRadiusPx * scaleFactor,
                 constraintSet,
             )
         }
     }
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
-        constraintLayout.removeView(lockIconViewId)
+        constraintLayout.removeView(deviceEntryIconViewId)
     }
 
     @VisibleForTesting
-    internal fun centerLockIcon(center: Point, radius: Float, constraintSet: ConstraintSet) {
+    internal fun centerIcon(center: Point, radius: Float, constraintSet: ConstraintSet) {
         val sensorRect =
             Rect().apply {
                 set(
@@ -119,17 +149,17 @@
             }
 
         constraintSet.apply {
-            constrainWidth(lockIconViewId, sensorRect.right - sensorRect.left)
-            constrainHeight(lockIconViewId, sensorRect.bottom - sensorRect.top)
+            constrainWidth(deviceEntryIconViewId, sensorRect.right - sensorRect.left)
+            constrainHeight(deviceEntryIconViewId, sensorRect.bottom - sensorRect.top)
             connect(
-                lockIconViewId,
+                deviceEntryIconViewId,
                 ConstraintSet.TOP,
                 ConstraintSet.PARENT_ID,
                 ConstraintSet.TOP,
                 sensorRect.top
             )
             connect(
-                lockIconViewId,
+                deviceEntryIconViewId,
                 ConstraintSet.START,
                 ConstraintSet.PARENT_ID,
                 ConstraintSet.START,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
new file mode 100644
index 0000000..842dde3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -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.systemui.keyguard.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+@ExperimentalCoroutinesApi
+class DeviceEntryIconViewModel @Inject constructor() {
+    // TODO: b/305234447 update these states from the data layer
+    val iconViewModel: Flow<IconViewModel> =
+        flowOf(
+            IconViewModel(
+                type = DeviceEntryIconView.IconType.LOCK,
+                useAodVariant = false,
+                tint = Color.WHITE,
+                alpha = 1f,
+                padding = 48,
+            )
+        )
+    val backgroundViewModel: Flow<BackgroundViewModel> =
+        flowOf(BackgroundViewModel(alpha = 1f, tint = Color.GRAY))
+    val burnInViewModel: Flow<BurnInViewModel> = flowOf(BurnInViewModel(0, 0, 0f))
+    val isLongPressEnabled: Flow<Boolean> = flowOf(true)
+    val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
+        flowOf(DeviceEntryIconView.AccessibilityHintType.NONE)
+
+    fun onLongPress() {
+        // TODO() vibrate & perform action based on current lock/unlock state
+    }
+    data class BurnInViewModel(
+        val x: Int, // current x burn in offset based on the aodTransitionAmount
+        val y: Int, // current y burn in offset based on the aodTransitionAmount
+        val progress: Float, // current progress based on the aodTransitionAmount
+    )
+
+    class IconViewModel(
+        val type: DeviceEntryIconView.IconType,
+        val useAodVariant: Boolean,
+        val tint: Int,
+        val alpha: Float,
+        val padding: Int,
+    )
+
+    class BackgroundViewModel(
+        val alpha: Float,
+        val tint: Int,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 4aad6a0..35c2b06 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -170,6 +170,7 @@
     private CommandQueue mCommandQueue;
 
     private View mRootView;
+    private View mFooterActionsView;
 
     @Inject
     public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -285,6 +286,7 @@
         if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)
                 || !ComposeFacade.INSTANCE.isComposeAvailable()) {
             Log.d(TAG, "Binding the View implementation of the QS footer actions");
+            mFooterActionsView = footerActionsView;
             mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
                     mListeningAndVisibilityLifecycleOwner);
             return;
@@ -294,6 +296,7 @@
         Log.d(TAG, "Binding the Compose implementation of the QS footer actions");
         View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(),
                 mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner);
+        mFooterActionsView = composeView;
 
         // The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin
         // to all views except for qs_footer_actions, so we set it to the Compose view.
@@ -472,7 +475,7 @@
         boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
                 || mHeaderAnimating || mShowCollapsedOnKeyguard);
         mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
-        mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
+        mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
         mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
                 || (mQsExpanded && !mStackScrollerOverscrolling));
         mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
@@ -856,7 +859,7 @@
         boolean customizing = isCustomizing();
         mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
-        mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing);
+        mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         // Let the panel know the position changed and it needs to update where notifications
         // and whatnot are.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index d09b210..0995dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -24,13 +24,11 @@
 import android.widget.ImageView
 import android.widget.LinearLayout
 import android.widget.TextView
-import androidx.core.view.isInvisible
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.dagger.SysUISingleton
@@ -40,6 +38,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlin.math.roundToInt
 import kotlinx.coroutines.launch
@@ -98,10 +97,6 @@
         var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
         var previousUserSwitcher: FooterActionsButtonViewModel? = null
 
-        // Set the initial visibility on the View directly so that we don't briefly show it for a
-        // few frames before [viewModel.isVisible] is collected.
-        view.isInvisible = !viewModel.isVisible.value
-
         // Listen for ViewModel updates when the View is attached.
         view.repeatWhenAttached {
             val attachedScope = this.lifecycleScope
@@ -111,12 +106,7 @@
                 // TODO(b/242040009): Should this move somewhere else?
                 launch { viewModel.observeDeviceMonitoringDialogRequests(view.context) }
 
-                // Make sure we set the correct visibility and alpha even when QS are not currently
-                // shown.
-                launch {
-                    viewModel.isVisible.collect { isVisible -> view.isInvisible = !isVisible }
-                }
-
+                // Make sure we set the correct alphas even when QS are not currently shown.
                 launch { viewModel.alpha.collect { view.alpha = it } }
                 launch {
                     viewModel.backgroundAlpha.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index eff3e76..aff4a67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -77,14 +77,6 @@
      */
     val observeDeviceMonitoringDialogRequests: suspend (quickSettingsContext: Context) -> Unit,
 ) {
-    /**
-     * Whether the UI rendering this ViewModel should be visible. Note that even when this is false,
-     * the UI should still participate to the layout it is included in (i.e. in the View world it
-     * should be INVISIBLE, not GONE).
-     */
-    private val _isVisible = MutableStateFlow(false)
-    val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
-
     /** The alpha the UI rendering this ViewModel should have. */
     private val _alpha = MutableStateFlow(1f)
     val alpha: StateFlow<Float> = _alpha.asStateFlow()
@@ -93,10 +85,6 @@
     private val _backgroundAlpha = MutableStateFlow(1f)
     val backgroundAlpha: StateFlow<Float> = _backgroundAlpha.asStateFlow()
 
-    fun onVisibilityChangeRequested(visible: Boolean) {
-        _isVisible.value = visible
-    }
-
     /** Called when the expansion of the Quick Settings changed. */
     fun onQuickSettingsExpansionChanged(expansion: Float, isInSplitShade: Boolean) {
         if (isInSplitShade) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 4284c96c..dd32434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -481,7 +481,7 @@
             hostLayout.addView(expected, i);
         }
         hostLayout.setChangingViewPositions(false);
-        hostLayout.setReplacingIcons(null);
+        hostLayout.setReplacingIconsLegacy(null);
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
index a5d7457..6cd7406 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -46,6 +47,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 
+@ExperimentalCoroutinesApi
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardKeyEventInteractorTest : SysuiTestCase() {
@@ -128,58 +130,62 @@
     }
 
     @Test
-    fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() {
+    fun dispatchKeyEvent_menuActionUp_awakeKeyguard_showsPrimaryBouncer() {
         powerInteractor.setAwakeForTest()
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
         whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
 
-        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
-        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
-        verify(shadeController).animateCollapseShadeForced()
+        verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU)
     }
 
     @Test
-    fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() {
+    fun dispatchKeyEvent_menuActionUp_awakeShadeLocked_collapsesShade() {
         powerInteractor.setAwakeForTest()
         whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
         whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
 
-        // action down: does NOT collapse the shade
-        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)
-        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
-        verify(shadeController, never()).animateCollapseShadeForced()
-
-        // action up: collapses the shade
-        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
-        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
-        verify(shadeController).animateCollapseShadeForced()
+        verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU)
     }
 
     @Test
-    fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() {
+    fun dispatchKeyEvent_menuActionUp_asleepKeyguard_neverCollapsesShade() {
         powerInteractor.setAsleepForTest()
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
         whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
 
-        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
-        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
-        verify(shadeController, never()).animateCollapseShadeForced()
+        verifyActionsDoNothing(KeyEvent.KEYCODE_MENU)
     }
 
     @Test
-    fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() {
+    fun dispatchKeyEvent_spaceActionUp_awakeKeyguard_collapsesShade() {
         powerInteractor.setAwakeForTest()
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
 
-        // action down: does NOT collapse the shade
-        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE)
-        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
-        verify(shadeController, never()).animateCollapseShadeForced()
+        verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE)
+    }
 
-        // action up: collapses the shade
-        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)
-        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
-        verify(shadeController).animateCollapseShadeForced()
+    @Test
+    fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() {
+        powerInteractor.setAwakeForTest()
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+
+        verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE)
+    }
+
+    @Test
+    fun dispatchKeyEvent_enterActionUp_awakeKeyguard_showsPrimaryBouncer() {
+        powerInteractor.setAwakeForTest()
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+        verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER)
+    }
+
+    @Test
+    fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() {
+        powerInteractor.setAwakeForTest()
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+
+        verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER)
     }
 
     @Test
@@ -249,4 +255,42 @@
             .isFalse()
         verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any())
     }
+
+    private fun verifyActionUpCollapsesTheShade(keycode: Int) {
+        // action down: does NOT collapse the shade
+        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
+
+        // action up: collapses the shade
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(shadeController).animateCollapseShadeForced()
+    }
+
+    private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) {
+        // action down: does NOT collapse the shade
+        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+        verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+
+        // action up: collapses the shade
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true))
+    }
+
+    private fun verifyActionsDoNothing(keycode: Int) {
+        // action down: does nothing
+        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
+        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
+        verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+
+        // action up: doesNothing
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
+        verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 50ee026..8cfa87d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -30,8 +30,8 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
@@ -55,7 +55,7 @@
     private lateinit var underTest: DefaultKeyguardBlueprint
     private lateinit var rootView: KeyguardRootView
     @Mock private lateinit var defaultIndicationAreaSection: DefaultIndicationAreaSection
-    @Mock private lateinit var defaultLockIconSection: DefaultLockIconSection
+    @Mock private lateinit var mDefaultDeviceEntryIconSection: DefaultDeviceEntryIconSection
     @Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection
     @Mock
     private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection
@@ -75,7 +75,7 @@
         underTest =
             DefaultKeyguardBlueprint(
                 defaultIndicationAreaSection,
-                defaultLockIconSection,
+                mDefaultDeviceEntryIconSection,
                 defaultShortcutsSection,
                 defaultAmbientIndicationAreaSection,
                 defaultSettingsPopupMenuSection,
@@ -101,14 +101,14 @@
         val prevBlueprint = mock(KeyguardBlueprint::class.java)
         val someSection = mock(KeyguardSection::class.java)
         whenever(prevBlueprint.sections)
-            .thenReturn(underTest.sections.minus(defaultLockIconSection).plus(someSection))
+            .thenReturn(underTest.sections.minus(mDefaultDeviceEntryIconSection).plus(someSection))
         val constraintLayout = ConstraintLayout(context, null)
         underTest.replaceViews(prevBlueprint, constraintLayout)
-        underTest.sections.minus(defaultLockIconSection).forEach {
+        underTest.sections.minus(mDefaultDeviceEntryIconSection).forEach {
             verify(it, never()).addViews(constraintLayout)
         }
 
-        verify(defaultLockIconSection).addViews(constraintLayout)
+        verify(mDefaultDeviceEntryIconSection).addViews(constraintLayout)
         verify(someSection).removeViews(constraintLayout)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
similarity index 65%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
index 7495637..5f22c7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt
@@ -24,14 +24,17 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.LockIconViewController
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -40,35 +43,54 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@ExperimentalCoroutinesApi
 @RunWith(JUnit4::class)
 @SmallTest
-class DefaultLockIconSectionTest : SysuiTestCase() {
+class DefaultDeviceEntryIconSectionTest : SysuiTestCase() {
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var authController: AuthController
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
     @Mock private lateinit var notificationPanelView: NotificationPanelView
-    @Mock private lateinit var featureFlags: FeatureFlags
+    private lateinit var featureFlags: FakeFeatureFlags
     @Mock private lateinit var lockIconViewController: LockIconViewController
-    private lateinit var underTest: DefaultLockIconSection
+    @Mock private lateinit var falsingManager: FalsingManager
+    private lateinit var underTest: DefaultDeviceEntryIconSection
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        featureFlags =
+            FakeFeatureFlagsClassic().apply {
+                set(Flags.MIGRATE_LOCK_ICON, false)
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false)
+                set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
+            }
         underTest =
-            DefaultLockIconSection(
+            DefaultDeviceEntryIconSection(
                 keyguardUpdateMonitor,
                 authController,
                 windowManager,
                 context,
                 notificationPanelView,
                 featureFlags,
-                lockIconViewController
+                { lockIconViewController },
+                { DeviceEntryIconViewModel() },
+                { falsingManager },
             )
     }
 
     @Test
-    fun addViewsConditionally() {
-        whenever(featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)).thenReturn(true)
+    fun addViewsConditionally_migrateFlagOn() {
+        featureFlags.set(Flags.MIGRATE_LOCK_ICON, true)
+        val constraintLayout = ConstraintLayout(context, null)
+        underTest.addViews(constraintLayout)
+        assertThat(constraintLayout.childCount).isGreaterThan(0)
+    }
+
+    @Test
+    fun addViewsConditionally_migrateAndRefactorFlagsOn() {
+        featureFlags.set(Flags.MIGRATE_LOCK_ICON, true)
+        featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
         val constraintLayout = ConstraintLayout(context, null)
         underTest.addViews(constraintLayout)
         assertThat(constraintLayout.childCount).isGreaterThan(0)
@@ -76,7 +98,8 @@
 
     @Test
     fun addViewsConditionally_migrateFlagOff() {
-        whenever(featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)).thenReturn(false)
+        featureFlags.set(Flags.MIGRATE_LOCK_ICON, false)
+        featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false)
         val constraintLayout = ConstraintLayout(context, null)
         underTest.addViews(constraintLayout)
         assertThat(constraintLayout.childCount).isEqualTo(0)
@@ -87,18 +110,18 @@
         val cs = ConstraintSet()
         underTest.applyConstraints(cs)
 
-        val constraint = cs.getConstraint(R.id.lock_icon_view)
+        val constraint = cs.getConstraint(R.id.device_entry_icon_view)
 
         assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
         assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
     }
 
     @Test
-    fun testCenterLockIcon() {
+    fun testCenterIcon() {
         val cs = ConstraintSet()
-        underTest.centerLockIcon(Point(5, 6), 1F, cs)
+        underTest.centerIcon(Point(5, 6), 1F, cs)
 
-        val constraint = cs.getConstraint(R.id.lock_icon_view)
+        val constraint = cs.getConstraint(R.id.device_entry_icon_view)
 
         assertThat(constraint.layout.mWidth).isEqualTo(2)
         assertThat(constraint.layout.mHeight).isEqualTo(2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 0e6e4fa..c711806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -373,18 +373,6 @@
     }
 
     @Test
-    fun isVisible() {
-        val underTest = utils.footerActionsViewModel()
-        assertThat(underTest.isVisible.value).isFalse()
-
-        underTest.onVisibilityChangeRequested(visible = true)
-        assertThat(underTest.isVisible.value).isTrue()
-
-        underTest.onVisibilityChangeRequested(visible = false)
-        assertThat(underTest.isVisible.value).isFalse()
-    }
-
-    @Test
     fun alpha_inSplitShade_followsExpansion() {
         val underTest = utils.footerActionsViewModel()
 
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/services/Android.bp b/services/Android.bp
index 3ae9360..aca8409 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -238,12 +238,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/core/Android.bp b/services/core/Android.bp
index 975b1e8..961e9d3 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -197,6 +197,7 @@
         "android.hardware.power.stats-V2-java",
         "android.hidl.manager-V1.2-java",
         "cbor-java",
+        "dropbox_flags_lib",
         "icu4j_calendar_astronomer",
         "android.security.aaid_aidl-java",
         "netd-client",
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 55069b7..f82a6aa 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -16,10 +16,14 @@
 
 package com.android.server;
 
+import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -30,6 +34,7 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.Debug;
@@ -66,6 +71,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.ObjectUtils;
 import com.android.server.DropBoxManagerInternal.EntrySource;
+import com.android.server.feature.flags.Flags;
 
 import libcore.io.IoUtils;
 
@@ -89,6 +95,13 @@
  * Clients use {@link DropBoxManager} to access this service.
  */
 public final class DropBoxManagerService extends SystemService {
+    /**
+     * For Android U and earlier versions, apps can continue to use the READ_LOGS permission,
+     * but for all subsequent versions, the READ_DROPBOX_DATA permission must be used.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static final long ENFORCE_READ_DROPBOX_DATA = 296060945L;
     private static final String TAG = "DropBoxManagerService";
     private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
     private static final int DEFAULT_MAX_FILES = 1000;
@@ -109,7 +122,6 @@
     // Tags that we should drop by default.
     private static final List<String> DISABLED_BY_DEFAULT_TAGS =
             List.of("data_app_wtf", "system_app_wtf", "system_server_wtf");
-
     // TODO: This implementation currently uses one file per entry, which is
     // inefficient for smallish entries -- consider using a single queue file
     // per tag (or even globally) instead.
@@ -291,8 +303,21 @@
             if (!DropBoxManagerService.this.mBooted) {
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             }
-            getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
-                    android.Manifest.permission.READ_LOGS, options);
+            if (Flags.enableReadDropboxPermission()) {
+                BroadcastOptions unbundledOptions = (options == null)
+                        ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options);
+
+                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true);
+                getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
+                        Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle());
+
+                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false);
+                getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
+                        Manifest.permission.READ_LOGS, unbundledOptions.toBundle());
+            } else {
+                getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
+                        android.Manifest.permission.READ_LOGS, options);
+            }
         }
 
         private Intent createIntent(String tag, long time) {
@@ -572,9 +597,16 @@
             return true;
         }
 
+
+        String permission = Manifest.permission.READ_LOGS;
+        if (Flags.enableReadDropboxPermission()
+                && CompatChanges.isChangeEnabled(ENFORCE_READ_DROPBOX_DATA, callingUid)) {
+            permission = Manifest.permission.READ_DROPBOX_DATA;
+        }
+
         // Callers always need this permission
-        getContext().enforceCallingOrSelfPermission(
-                android.Manifest.permission.READ_LOGS, TAG);
+        getContext().enforceCallingOrSelfPermission(permission, TAG);
+
 
         // Callers also need the ability to read usage statistics
         switch (getContext().getSystemService(AppOpsManager.class).noteOp(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 3af0e8c..15fc2dc 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3007,7 +3007,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;
@@ -3231,12 +3231,12 @@
 
     @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);
+            mVold.createUserStorageKeys(userId, serialNumber, ephemeral);
             // Since the user's CE key was just created, the user's CE storage is now unlocked.
             synchronized (mLock) {
                 mCeUnlockedUsers.append(userId);
@@ -3248,12 +3248,12 @@
 
     @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);
+            mVold.destroyUserStorageKeys(userId);
             // Since the user's CE key was just destroyed, the user's CE storage is now locked.
             synchronized (mLock) {
                 mCeUnlockedUsers.remove(userId);
@@ -3266,21 +3266,22 @@
     /* 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) {
             mCeUnlockedUsers.append(userId);
@@ -3289,23 +3290,22 @@
 
     @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;
@@ -3317,7 +3317,7 @@
     }
 
     @Override
-    public boolean isUserKeyUnlocked(int userId) {
+    public boolean isCeStorageUnlocked(int userId) {
         synchronized (mLock) {
             return mCeUnlockedUsers.contains(userId);
         }
@@ -3719,8 +3719,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);
         }
 
@@ -3846,15 +3846,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);
         }
@@ -3914,7 +3914,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) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ef67cbe..e88d0c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2299,7 +2299,7 @@
             return;
         }
         // TODO(b/148767783): should we check all profiles under user0?
-        UserspaceRebootLogger.logEventAsync(StorageManager.isUserKeyUnlocked(userId),
+        UserspaceRebootLogger.logEventAsync(StorageManager.isCeStorageUnlocked(userId),
                 BackgroundThread.getExecutor());
     }
 
@@ -4648,7 +4648,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);
@@ -9718,9 +9718,29 @@
     public ParceledListSlice<ApplicationStartInfo> getHistoricalProcessStartReasons(
             String packageName, int maxNum, int userId) {
         enforceNotIsolatedCaller("getHistoricalProcessStartReasons");
+        // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+        if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+            throw new IllegalArgumentException("Unsupported userId");
+        }
+
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_NON_FULL,
+                "getHistoricalProcessStartReasons", null);
 
         final ArrayList<ApplicationStartInfo> results = new ArrayList<ApplicationStartInfo>();
-
+        if (!TextUtils.isEmpty(packageName)) {
+            final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid,
+                        "getHistoricalProcessStartReasons");
+            if (uid != INVALID_UID) {
+                mProcessList.mAppStartInfoTracker.getStartInfo(
+                        packageName, userId, callingPid, maxNum, results);
+            }
+        } else {
+            // If no package name is given, use the caller's uid as the filter uid.
+            mProcessList.mAppStartInfoTracker.getStartInfo(
+                    packageName, callingUid, callingPid, maxNum, results);
+        }
         return new ParceledListSlice<ApplicationStartInfo>(results);
     }
 
@@ -9729,6 +9749,14 @@
     public void setApplicationStartInfoCompleteListener(
             IApplicationStartInfoCompleteListener listener, int userId) {
         enforceNotIsolatedCaller("setApplicationStartInfoCompleteListener");
+
+        // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+        if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+            throw new IllegalArgumentException("Unsupported userId");
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        mProcessList.mAppStartInfoTracker.addStartInfoCompleteListener(listener, callingUid);
     }
 
 
@@ -9742,6 +9770,7 @@
         }
 
         final int callingUid = Binder.getCallingUid();
+        mProcessList.mAppStartInfoTracker.clearStartInfoCompleteListener(callingUid, true);
     }
 
     @Override
@@ -10043,6 +10072,8 @@
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
+                mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
+                pw.println("-------------------------------------------------------------------------------");
                 mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
             }
             if (dumpPackage == null) {
@@ -10439,6 +10470,12 @@
                 LockGuard.dump(fd, pw, args);
             } else if ("users".equals(cmd)) {
                 dumpUsers(pw);
+            } else if ("start-info".equals(cmd)) {
+                if (opti < args.length) {
+                    dumpPackage = args[opti];
+                    opti++;
+                }
+                mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
             } else if ("exit-info".equals(cmd)) {
                 if (opti < args.length) {
                     dumpPackage = args[opti];
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a057f32..69bf612 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -272,6 +272,8 @@
                     return runSetWatchHeap(pw);
                 case "clear-watch-heap":
                     return runClearWatchHeap(pw);
+                case "clear-start-info":
+                    return runClearStartInfo(pw);
                 case "clear-exit-info":
                     return runClearExitInfo(pw);
                 case "bug-report":
@@ -1339,6 +1341,31 @@
         return 0;
     }
 
+    int runClearStartInfo(PrintWriter pw) throws RemoteException {
+        mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
+                "runClearStartInfo()");
+        String opt;
+        int userId = UserHandle.USER_CURRENT;
+        String packageName = null;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                packageName = opt;
+            }
+        }
+        if (userId == UserHandle.USER_CURRENT) {
+            UserInfo user = mInterface.getCurrentUser();
+            if (user == null) {
+                return -1;
+            }
+            userId = user.id;
+        }
+        mInternal.mProcessList.mAppStartInfoTracker
+                .clearHistoryProcessStartInfo(packageName, userId);
+        return 0;
+    }
+
     int runClearExitInfo(PrintWriter pw) throws RemoteException {
         mInternal.enforceCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
                 "runClearExitInfo()");
@@ -4090,6 +4117,7 @@
             pw.println("    s[ervices] [COMP_SPEC ...]: service state");
             pw.println("    allowed-associations: current package association restrictions");
             pw.println("    as[sociations]: tracked app associations");
+            pw.println("    start-info [PACKAGE_NAME]: historical process start information");
             pw.println("    exit-info [PACKAGE_NAME]: historical process exit information");
             pw.println("    lmk: stats on low memory killer");
             pw.println("    lru: raw LRU process list");
@@ -4265,6 +4293,8 @@
             pw.println("      above <HEAP-LIMIT> then a heap dump is collected for the user to report.");
             pw.println("  clear-watch-heap");
             pw.println("      Clear the previously set-watch-heap.");
+            pw.println("  clear-start-info [--user <USER_ID> | all | current] [package]");
+            pw.println("      Clear the process start-info for given package");
             pw.println("  clear-exit-info [--user <USER_ID> | all | current] [package]");
             pw.println("      Clear the process exit-info for given package");
             pw.println("  bug-report [--progress | --telephony]");
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
new file mode 100644
index 0000000..edca74f
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -0,0 +1,989 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ApplicationStartInfo.START_TIMESTAMP_LAUNCH;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.app.ActivityOptions;
+import android.app.ApplicationStartInfo;
+import android.app.Flags;
+import android.app.IApplicationStartInfoCompleteListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.icu.text.SimpleDateFormat;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ProcessMap;
+import com.android.server.IoThread;
+import com.android.server.ServiceThread;
+import com.android.server.SystemServiceManager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiFunction;
+
+/** A class to manage all the {@link android.app.ApplicationStartInfo} records. */
+public final class AppStartInfoTracker {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AppStartInfoTracker" : TAG_AM;
+
+    /** Interval of persisting the app start info to persistent storage. */
+    private static final long APP_START_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30);
+
+    /** These are actions that the forEach* should take after each iteration */
+    private static final int FOREACH_ACTION_NONE = 0;
+    private static final int FOREACH_ACTION_REMOVE_ITEM = 1;
+    private static final int FOREACH_ACTION_STOP_ITERATION = 2;
+
+    private static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
+
+    @VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore";
+
+    @VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo";
+
+    private final Object mLock = new Object();
+
+    private boolean mEnabled = false;
+
+    /** Initialized in {@link #init} and read-only after that. */
+    private ActivityManagerService mService;
+
+    /** Initialized in {@link #init} and read-only after that. */
+    private Handler mHandler;
+
+    /** The task to persist app process start info */
+    @GuardedBy("mLock")
+    private Runnable mAppStartInfoPersistTask = null;
+
+    /**
+     * Last time(in ms) since epoch that the app start info was persisted into persistent storage.
+     */
+    @GuardedBy("mLock")
+    private long mLastAppStartInfoPersistTimestamp = 0L;
+
+    /**
+     * Retention policy: keep up to X historical start info per package.
+     *
+     * <p>Initialized in {@link #init} and read-only after that. No lock is needed.
+     */
+    private int mAppStartInfoHistoryListSize;
+
+    @GuardedBy("mLock")
+    private final ProcessMap<AppStartInfoContainer> mData;
+
+    /** UID as key. */
+    @GuardedBy("mLock")
+    private final SparseArray<ApplicationStartInfoCompleteCallback> mCallbacks;
+
+    /**
+     * Whether or not we've loaded the historical app process start info from persistent storage.
+     */
+    @VisibleForTesting AtomicBoolean mAppStartInfoLoaded = new AtomicBoolean();
+
+    /** Temporary list being used to filter/sort intermediate results in {@link #getStartInfo}. */
+    @GuardedBy("mLock")
+    final ArrayList<ApplicationStartInfo> mTmpStartInfoList = new ArrayList<>();
+
+    /**
+     * The path to the directory which includes the historical proc start info file as specified in
+     * {@link #mProcStartInfoFile}.
+     */
+    @VisibleForTesting File mProcStartStoreDir;
+
+    /** The path to the historical proc start info file, persisted in the storage. */
+    @VisibleForTesting File mProcStartInfoFile;
+
+    AppStartInfoTracker() {
+        mCallbacks = new SparseArray<>();
+        mData = new ProcessMap<AppStartInfoContainer>();
+    }
+
+    void init(ActivityManagerService service) {
+        mService = service;
+
+        ServiceThread thread =
+                new ServiceThread(TAG + ":handler", THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
+        thread.start();
+        mHandler = new Handler(thread.getLooper());
+
+        mProcStartStoreDir = new File(SystemServiceManager.ensureSystemDir(), APP_START_STORE_DIR);
+        if (!FileUtils.createDir(mProcStartStoreDir)) {
+            Slog.e(TAG, "Unable to create " + mProcStartStoreDir);
+            return;
+        }
+        mProcStartInfoFile = new File(mProcStartStoreDir, APP_START_INFO_FILE);
+
+        mAppStartInfoHistoryListSize = APP_START_INFO_HISTORY_LIST_SIZE;
+    }
+
+    void onSystemReady() {
+        mEnabled = Flags.appStartInfo();
+        if (!mEnabled) {
+            return;
+        }
+
+        registerForUserRemoval();
+        registerForPackageRemoval();
+        IoThread.getHandler().post(() -> {
+            loadExistingProcessStartInfo();
+        });
+    }
+
+    void handleProcessColdStarted(long startTimeNs, HostingRecord hostingRecord,
+            ProcessRecord app) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            addBaseFieldsFromProcessRecord(start, app);
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_FORK, app.getStartElapsedTime());
+            start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+            start.setReason(ApplicationStartInfo.START_REASON_OTHER);
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessActivityWarmOrHotStarted(long startTimeNs,
+            ActivityOptions activityOptions, Intent intent) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.setIntent(intent);
+            start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
+            if (activityOptions != null) {
+                start.setProcessName(activityOptions.getPackageName());
+            }
+            start.setStartType(ApplicationStartInfo.START_TYPE_WARM);
+            if (intent != null && intent.getCategories() != null
+                    && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+                start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
+            } else {
+                start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY);
+            }
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessActivityStartedFromRecents(long startTimeNs,
+            ActivityOptions activityOptions) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            if (activityOptions != null) {
+                start.setIntent(activityOptions.getResultData());
+                start.setProcessName(activityOptions.getPackageName());
+            }
+            start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER_RECENTS);
+            start.setStartType(ApplicationStartInfo.START_TYPE_WARM);
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
+                ServiceRecord serviceRecord, HostingRecord hostingRecord, boolean cold) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            addBaseFieldsFromProcessRecord(start, app);
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
+                    : ApplicationStartInfo.START_TYPE_WARM);
+            start.setReason(serviceRecord.permission != null
+                    && serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
+                    ? ApplicationStartInfo.START_REASON_JOB
+                    : ApplicationStartInfo.START_REASON_SERVICE);
+            start.setIntent(serviceRecord.intent.getIntent());
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app,
+                BroadcastRecord broadcast, boolean cold) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            addBaseFieldsFromProcessRecord(start, app);
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
+                    : ApplicationStartInfo.START_TYPE_WARM);
+            if (broadcast == null) {
+                start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
+            } else if (broadcast.alarm) {
+                start.setReason(ApplicationStartInfo.START_REASON_ALARM);
+            } else if (broadcast.pushMessage || broadcast.pushMessageOverQuota) {
+                start.setReason(ApplicationStartInfo.START_REASON_PUSH);
+            } else if (Intent.ACTION_BOOT_COMPLETED.equals(broadcast.intent.getAction())) {
+                start.setReason(ApplicationStartInfo.START_REASON_BOOT_COMPLETE);
+            } else {
+                start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
+            }
+            start.setIntent(broadcast != null ? broadcast.intent : null);
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app,
+                boolean cold) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            addBaseFieldsFromProcessRecord(start, app);
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
+                    : ApplicationStartInfo.START_TYPE_WARM);
+            start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER);
+            addStartInfoLocked(start);
+        }
+    }
+
+    public void handleProcessBackupStart(long startTimeNs, ProcessRecord app,
+                BackupRecord backupRecord, boolean cold) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            ApplicationStartInfo start = new ApplicationStartInfo();
+            addBaseFieldsFromProcessRecord(start, app);
+            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
+            start.addStartupTimestamp(
+                ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
+            start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
+                    : ApplicationStartInfo.START_TYPE_WARM);
+            start.setReason(ApplicationStartInfo.START_REASON_BACKUP);
+            addStartInfoLocked(start);
+        }
+    }
+
+    private void addBaseFieldsFromProcessRecord(ApplicationStartInfo start, ProcessRecord app) {
+        if (app == null) {
+            return;
+        }
+        final int definingUid = app.getHostingRecord() != null
+                ? app.getHostingRecord().getDefiningUid() : 0;
+        start.setPid(app.getPid());
+        start.setRealUid(app.uid);
+        start.setPackageUid(app.info.uid);
+        start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
+        start.setProcessName(app.processName);
+    }
+
+    void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) {
+        if (!mEnabled) {
+            return;
+        }
+        addTimestampToStart(app, timeNs,
+                ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE);
+    }
+
+    void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) {
+        addTimestampToStart(app, timeNs,
+                ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION);
+    }
+
+    void reportFirstFrameTimeNanos(ProcessRecord app, long timeNs) {
+        if (!mEnabled) {
+            return;
+        }
+        addTimestampToStart(app, timeNs,
+                ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME);
+    }
+
+    void reportFullyDrawnTimeNanos(ProcessRecord app, long timeNs) {
+        if (!mEnabled) {
+            return;
+        }
+        addTimestampToStart(app, timeNs,
+                ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN);
+    }
+
+    void reportFullyDrawnTimeNanos(String processName, int uid, long timeNs) {
+        if (!mEnabled) {
+            return;
+        }
+        addTimestampToStart(processName, uid, timeNs,
+                ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN);
+    }
+
+    private void addTimestampToStart(ProcessRecord app, long timeNs, int key) {
+        addTimestampToStart(app.processName, app.uid, timeNs, key);
+    }
+
+    private void addTimestampToStart(String processName, int uid, long timeNs, int key) {
+        synchronized (mLock) {
+            AppStartInfoContainer container = mData.get(processName, uid);
+            if (container == null) {
+                // Record was not created, discard new data.
+                return;
+            }
+            container.addTimestampToStartLocked(key, timeNs);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private ApplicationStartInfo addStartInfoLocked(ApplicationStartInfo raw) {
+        if (!mAppStartInfoLoaded.get()) {
+            //records added before initial load from storage will be lost.
+            Slog.w(TAG, "Skipping saving the start info due to ongoing loading from storage");
+            return null;
+        }
+
+        final ApplicationStartInfo info = new ApplicationStartInfo(raw);
+
+        AppStartInfoContainer container = mData.get(raw.getProcessName(), raw.getRealUid());
+        if (container == null) {
+            container = new AppStartInfoContainer(mAppStartInfoHistoryListSize);
+            container.mUid = info.getRealUid();
+            mData.put(raw.getProcessName(), raw.getRealUid(), container);
+        }
+        container.addStartInfoLocked(info);
+
+        schedulePersistProcessStartInfo(false);
+
+        return info;
+    }
+
+    /**
+     * Called whenever data is added to a {@link ApplicationStartInfo} object. Checks for
+     * completeness and triggers callback if a callback has been registered and the object
+     * is complete.
+     */
+    private void checkCompletenessAndCallback(ApplicationStartInfo startInfo) {
+        synchronized (mLock) {
+            if (startInfo.getStartupState()
+                    == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) {
+                ApplicationStartInfoCompleteCallback callback =
+                        mCallbacks.get(startInfo.getRealUid());
+                if (callback != null) {
+                    callback.onApplicationStartInfoComplete(startInfo);
+                }
+            }
+        }
+    }
+
+    void getStartInfo(String packageName, int filterUid, int filterPid,
+            int maxNum, ArrayList<ApplicationStartInfo> results) {
+        if (!mEnabled) {
+            return;
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                boolean emptyPackageName = TextUtils.isEmpty(packageName);
+                if (!emptyPackageName) {
+                    // fast path
+                    AppStartInfoContainer container = mData.get(packageName, filterUid);
+                    if (container != null) {
+                        container.getStartInfoLocked(filterPid, maxNum, results);
+                    }
+                } else {
+                    // slow path
+                    final ArrayList<ApplicationStartInfo> list = mTmpStartInfoList;
+                    list.clear();
+                    // get all packages
+                    forEachPackageLocked(
+                            (name, records) -> {
+                                AppStartInfoContainer container = records.get(filterUid);
+                                if (container != null) {
+                                    list.addAll(container.mInfos);
+                                }
+                                return AppStartInfoTracker.FOREACH_ACTION_NONE;
+                            });
+
+                    Collections.sort(
+                            list, (a, b) ->
+                            Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
+                    int size = list.size();
+                    if (maxNum > 0) {
+                        size = Math.min(size, maxNum);
+                    }
+                    for (int i = 0; i < size; i++) {
+                        results.add(list.get(i));
+                    }
+                    list.clear();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    final class ApplicationStartInfoCompleteCallback implements DeathRecipient {
+        private final int mUid;
+        private final IApplicationStartInfoCompleteListener mCallback;
+
+        ApplicationStartInfoCompleteCallback(IApplicationStartInfoCompleteListener callback,
+                int uid) {
+            mCallback = callback;
+            mUid = uid;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+        }
+
+        void onApplicationStartInfoComplete(ApplicationStartInfo startInfo) {
+            try {
+                mCallback.onApplicationStartInfoComplete(startInfo);
+            } catch (RemoteException e) {
+                /*ignored*/
+            }
+            clearStartInfoCompleteListener(mUid, true);
+        }
+
+        void unlinkToDeath() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            clearStartInfoCompleteListener(mUid, false);
+        }
+    }
+
+    void addStartInfoCompleteListener(
+            final IApplicationStartInfoCompleteListener listener, final int uid) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            mCallbacks.put(uid, new ApplicationStartInfoCompleteCallback(listener, uid));
+        }
+    }
+
+    void clearStartInfoCompleteListener(final int uid, boolean unlinkDeathRecipient) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            if (unlinkDeathRecipient) {
+                ApplicationStartInfoCompleteCallback callback = mCallbacks.get(uid);
+                if (callback != null) {
+                    callback.unlinkToDeath();
+                }
+            }
+            mCallbacks.remove(uid);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void forEachPackageLocked(
+            BiFunction<String, SparseArray<AppStartInfoContainer>, Integer> callback) {
+        if (callback != null) {
+            ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap();
+            for (int i = map.size() - 1; i >= 0; i--) {
+                switch (callback.apply(map.keyAt(i), map.valueAt(i))) {
+                    case FOREACH_ACTION_REMOVE_ITEM:
+                        map.removeAt(i);
+                        break;
+                    case FOREACH_ACTION_STOP_ITERATION:
+                        i = 0;
+                        break;
+                    case FOREACH_ACTION_NONE:
+                    default:
+                        break;
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void removePackageLocked(String packageName, int uid, boolean removeUid, int userId) {
+        ArrayMap<String, SparseArray<AppStartInfoContainer>> map = mData.getMap();
+        SparseArray<AppStartInfoContainer> array = map.get(packageName);
+        if (array == null) {
+            return;
+        }
+        if (userId == UserHandle.USER_ALL) {
+            mData.getMap().remove(packageName);
+        } else {
+            for (int i = array.size() - 1; i >= 0; i--) {
+                if (UserHandle.getUserId(array.keyAt(i)) == userId) {
+                    array.removeAt(i);
+                    break;
+                }
+            }
+            if (array.size() == 0) {
+                map.remove(packageName);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void removeByUserIdLocked(final int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            mData.getMap().clear();
+            return;
+        }
+        forEachPackageLocked(
+                (packageName, records) -> {
+                    for (int i = records.size() - 1; i >= 0; i--) {
+                        if (UserHandle.getUserId(records.keyAt(i)) == userId) {
+                            records.removeAt(i);
+                            break;
+                        }
+                    }
+                    return records.size() == 0 ? FOREACH_ACTION_REMOVE_ITEM : FOREACH_ACTION_NONE;
+                });
+    }
+
+    @VisibleForTesting
+    void onUserRemoved(int userId) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            removeByUserIdLocked(userId);
+            schedulePersistProcessStartInfo(true);
+        }
+    }
+
+    @VisibleForTesting
+    void onPackageRemoved(String packageName, int uid, boolean allUsers) {
+        if (!mEnabled) {
+            return;
+        }
+        if (packageName != null) {
+            final boolean removeUid =
+                    TextUtils.isEmpty(mService.mPackageManagerInt.getNameForUid(uid));
+            synchronized (mLock) {
+                removePackageLocked(
+                        packageName,
+                        uid,
+                        removeUid,
+                        allUsers ? UserHandle.USER_ALL : UserHandle.getUserId(uid));
+                schedulePersistProcessStartInfo(true);
+            }
+        }
+    }
+
+    private void registerForUserRemoval() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        mService.mContext.registerReceiverForAllUsers(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                        if (userId < 1) return;
+                        onUserRemoved(userId);
+                    }
+                },
+                filter,
+                null,
+                mHandler);
+    }
+
+    private void registerForPackageRemoval() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addDataScheme("package");
+        mService.mContext.registerReceiverForAllUsers(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                        if (replacing) {
+                            return;
+                        }
+                        int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
+                        boolean allUsers =
+                                intent.getBooleanExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, false);
+                        onPackageRemoved(intent.getData().getSchemeSpecificPart(), uid, allUsers);
+                    }
+                },
+                filter,
+                null,
+                mHandler);
+    }
+
+    /**
+     * Load the existing {@link android.app.ApplicationStartInfo} records from persistent storage.
+     */
+    @VisibleForTesting
+    void loadExistingProcessStartInfo() {
+        if (!mEnabled) {
+            return;
+        }
+        if (!mProcStartInfoFile.canRead()) {
+            // If file can't be read, mark complete so we can begin accepting new records.
+            mAppStartInfoLoaded.set(true);
+            return;
+        }
+
+        FileInputStream fin = null;
+        try {
+            AtomicFile af = new AtomicFile(mProcStartInfoFile);
+            fin = af.openRead();
+            ProtoInputStream proto = new ProtoInputStream(fin);
+            for (int next = proto.nextField();
+                    next != ProtoInputStream.NO_MORE_FIELDS;
+                    next = proto.nextField()) {
+                switch (next) {
+                    case (int) AppsStartInfoProto.LAST_UPDATE_TIMESTAMP:
+                        synchronized (mLock) {
+                            mLastAppStartInfoPersistTimestamp =
+                                    proto.readLong(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP);
+                        }
+                        break;
+                    case (int) AppsStartInfoProto.PACKAGES:
+                        loadPackagesFromProto(proto, next);
+                        break;
+                }
+            }
+        } catch (IOException | IllegalArgumentException | WireTypeMismatchException
+                | ClassNotFoundException e) {
+            Slog.w(TAG, "Error in loading historical app start info from persistent storage: " + e);
+        } finally {
+            if (fin != null) {
+                try {
+                    fin.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        mAppStartInfoLoaded.set(true);
+    }
+
+    private void loadPackagesFromProto(ProtoInputStream proto, long fieldId)
+            throws IOException, WireTypeMismatchException, ClassNotFoundException {
+        long token = proto.start(fieldId);
+        String pkgName = "";
+        for (int next = proto.nextField();
+                next != ProtoInputStream.NO_MORE_FIELDS;
+                next = proto.nextField()) {
+            switch (next) {
+                case (int) AppsStartInfoProto.Package.PACKAGE_NAME:
+                    pkgName = proto.readString(AppsStartInfoProto.Package.PACKAGE_NAME);
+                    break;
+                case (int) AppsStartInfoProto.Package.USERS:
+                    AppStartInfoContainer container =
+                            new AppStartInfoContainer(mAppStartInfoHistoryListSize);
+                    int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS);
+                    synchronized (mLock) {
+                        mData.put(pkgName, uid, container);
+                    }
+                    break;
+            }
+        }
+        proto.end(token);
+    }
+
+    /** Persist the existing {@link android.app.ApplicationStartInfo} records to storage. */
+    @VisibleForTesting
+    void persistProcessStartInfo() {
+        if (!mEnabled) {
+            return;
+        }
+        AtomicFile af = new AtomicFile(mProcStartInfoFile);
+        FileOutputStream out = null;
+        long now = System.currentTimeMillis();
+        try {
+            out = af.startWrite();
+            ProtoOutputStream proto = new ProtoOutputStream(out);
+            proto.write(AppsStartInfoProto.LAST_UPDATE_TIMESTAMP, now);
+            synchronized (mLock) {
+                forEachPackageLocked(
+                        (packageName, records) -> {
+                            long token = proto.start(AppsStartInfoProto.PACKAGES);
+                            proto.write(AppsStartInfoProto.Package.PACKAGE_NAME, packageName);
+                            int uidArraySize = records.size();
+                            for (int j = 0; j < uidArraySize; j++) {
+                                try {
+                                    records.valueAt(j)
+                                        .writeToProto(proto, AppsStartInfoProto.Package.USERS);
+                                } catch (IOException e) {
+                                    Slog.w(TAG, "Unable to write app start info into persistent"
+                                            + "storage: " + e);
+                                }
+                            }
+                            proto.end(token);
+                            return AppStartInfoTracker.FOREACH_ACTION_NONE;
+                        });
+                mLastAppStartInfoPersistTimestamp = now;
+            }
+            proto.flush();
+            af.finishWrite(out);
+        } catch (IOException e) {
+            Slog.w(TAG, "Unable to write historical app start info into persistent storage: " + e);
+            af.failWrite(out);
+        }
+        synchronized (mLock) {
+            mAppStartInfoPersistTask = null;
+        }
+    }
+
+    /**
+     * Schedule a task to persist the {@link android.app.ApplicationStartInfo} records to storage.
+     */
+    @VisibleForTesting
+    void schedulePersistProcessStartInfo(boolean immediately) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            if (mAppStartInfoPersistTask == null || immediately) {
+                if (mAppStartInfoPersistTask != null) {
+                    IoThread.getHandler().removeCallbacks(mAppStartInfoPersistTask);
+                }
+                mAppStartInfoPersistTask = this::persistProcessStartInfo;
+                IoThread.getHandler()
+                        .postDelayed(
+                                mAppStartInfoPersistTask,
+                                immediately ? 0 : APP_START_INFO_PERSIST_INTERVAL);
+            }
+        }
+    }
+
+    /** Helper function for testing only. */
+    @VisibleForTesting
+    void clearProcessStartInfo(boolean removeFile) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            if (mAppStartInfoPersistTask != null) {
+                IoThread.getHandler().removeCallbacks(mAppStartInfoPersistTask);
+                mAppStartInfoPersistTask = null;
+            }
+            if (removeFile && mProcStartInfoFile != null) {
+                mProcStartInfoFile.delete();
+            }
+            mData.getMap().clear();
+        }
+    }
+
+    /**
+     * Helper functions for shell command.
+     * > adb shell dumpsys activity clear-start-info [package-name]
+     */
+    void clearHistoryProcessStartInfo(String packageName, int userId) {
+        if (!mEnabled) {
+            return;
+        }
+        Optional<Integer> appId = Optional.empty();
+        if (TextUtils.isEmpty(packageName)) {
+            synchronized (mLock) {
+                removeByUserIdLocked(userId);
+            }
+        } else {
+            final int uid =
+                    mService.mPackageManagerInt.getPackageUid(
+                            packageName, PackageManager.MATCH_ALL, userId);
+            appId = Optional.of(UserHandle.getAppId(uid));
+            synchronized (mLock) {
+                removePackageLocked(packageName, uid, true, userId);
+            }
+        }
+        schedulePersistProcessStartInfo(true);
+    }
+
+    /**
+     * Helper functions for shell command.
+     * > adb shell dumpsys activity start-info [package-name]
+     */
+    void dumpHistoryProcessStartInfo(PrintWriter pw, String packageName) {
+        if (!mEnabled) {
+            return;
+        }
+        pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity start-info)");
+        SimpleDateFormat sdf = new SimpleDateFormat();
+        synchronized (mLock) {
+            pw.println("Last Timestamp of Persistence Into Persistent Storage: "
+                    + sdf.format(new Date(mLastAppStartInfoPersistTimestamp)));
+            if (TextUtils.isEmpty(packageName)) {
+                forEachPackageLocked((name, records) -> {
+                    dumpHistoryProcessStartInfoLocked(pw, "  ", name, records, sdf);
+                    return AppStartInfoTracker.FOREACH_ACTION_NONE;
+                });
+            } else {
+                SparseArray<AppStartInfoContainer> array = mData.getMap().get(packageName);
+                if (array != null) {
+                    dumpHistoryProcessStartInfoLocked(pw, "  ", packageName, array, sdf);
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void dumpHistoryProcessStartInfoLocked(PrintWriter pw, String prefix,
+            String packageName, SparseArray<AppStartInfoContainer> array,
+            SimpleDateFormat sdf) {
+        pw.println(prefix + "package: " + packageName);
+        int size = array.size();
+        for (int i = 0; i < size; i++) {
+            pw.println(prefix + "  Historical Process Start for userId=" + array.keyAt(i));
+            array.valueAt(i).dumpLocked(pw, prefix + "    ", sdf);
+        }
+    }
+
+    /** Convenience method to obtain timestamp of beginning of start.*/
+    private static long getStartTimestamp(ApplicationStartInfo startInfo) {
+        return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH);
+    }
+
+    /** A container class of (@link android.app.ApplicationStartInfo) */
+    final class AppStartInfoContainer {
+        private List<ApplicationStartInfo> mInfos; // Always kept sorted by first timestamp.
+        private int mMaxCapacity;
+        private int mUid;
+
+        AppStartInfoContainer(final int maxCapacity) {
+            mInfos = new ArrayList<ApplicationStartInfo>();
+            mMaxCapacity = maxCapacity;
+        }
+
+        @GuardedBy("mLock")
+        void getStartInfoLocked(
+                final int filterPid, final int maxNum, ArrayList<ApplicationStartInfo> results) {
+            results.addAll(mInfos.size() <= maxNum ? 0 : mInfos.size() - maxNum, mInfos);
+        }
+
+        @GuardedBy("mLock")
+        void addStartInfoLocked(ApplicationStartInfo info) {
+            int size = mInfos.size();
+            if (size >= mMaxCapacity) {
+                // Remove oldest record if size is over max capacity.
+                int oldestIndex = -1;
+                long oldestTimeStamp = Long.MAX_VALUE;
+                for (int i = 0; i < size; i++) {
+                    ApplicationStartInfo startInfo = mInfos.get(i);
+                    if (getStartTimestamp(startInfo) < oldestTimeStamp) {
+                        oldestTimeStamp = getStartTimestamp(startInfo);
+                        oldestIndex = i;
+                    }
+                }
+                if (oldestIndex >= 0) {
+                    mInfos.remove(oldestIndex);
+                }
+                mInfos.remove(0);
+            }
+            mInfos.add(info);
+            Collections.sort(mInfos, (a, b) ->
+                    Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
+        }
+
+        @GuardedBy("mLock")
+        void addTimestampToStartLocked(int key, long timestampNs) {
+            int index = mInfos.size() - 1;
+            int startupState = mInfos.get(index).getStartupState();
+            if (startupState == ApplicationStartInfo.STARTUP_STATE_STARTED
+                    || key == ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) {
+                mInfos.get(index).addStartupTimestamp(key, timestampNs);
+            }
+        }
+
+        @GuardedBy("mLock")
+        void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
+            int size = mInfos.size();
+            for (int i = 0; i < size; i++) {
+                mInfos.get(i).dump(pw, prefix + "  ", "#" + i, sdf);
+            }
+        }
+
+        @GuardedBy("mLock")
+        void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException {
+            long token = proto.start(fieldId);
+            proto.write(AppsStartInfoProto.Package.User.UID, mUid);
+            int size = mInfos.size();
+            for (int i = 0; i < size; i++) {
+                mInfos.get(i)
+                        .writeToProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
+            }
+            proto.end(token);
+        }
+
+        int readFromProto(ProtoInputStream proto, long fieldId)
+                throws IOException, WireTypeMismatchException, ClassNotFoundException {
+            long token = proto.start(fieldId);
+            for (int next = proto.nextField();
+                    next != ProtoInputStream.NO_MORE_FIELDS;
+                    next = proto.nextField()) {
+                switch (next) {
+                    case (int) AppsStartInfoProto.Package.User.UID:
+                        mUid = proto.readInt(AppsStartInfoProto.Package.User.UID);
+                        break;
+                    case (int) AppsStartInfoProto.Package.User.APP_START_INFO:
+                        ApplicationStartInfo info = new ApplicationStartInfo();
+                        info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
+                        mInfos.add(info);
+                        break;
+                }
+            }
+            proto.end(token);
+            return mUid;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index f04198e..614caffe 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -494,6 +494,10 @@
     @GuardedBy("mService")
     final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>();
 
+    /** Manages the {@link android.app.ApplicationStartInfo} records. */
+    @GuardedBy("mAppStartInfoTracker")
+    final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker();
+
     /**
      * The currently running SDK sandbox processes for a uid.
      */
@@ -956,12 +960,14 @@
                         mSystemServerSocketForZygote.getFileDescriptor(),
                         EVENT_INPUT, this::handleZygoteMessages);
             }
+            mAppStartInfoTracker.init(mService);
             mAppExitInfoTracker.init(mService);
             mImperceptibleKillRunner = new ImperceptibleKillRunner(sKillThread.getLooper());
         }
     }
 
     void onSystemReady() {
+        mAppStartInfoTracker.onSystemReady();
         mAppExitInfoTracker.onSystemReady();
     }
 
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 0dd579f..728bace 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -658,8 +658,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) {
@@ -674,8 +674,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;
             }
 
@@ -709,8 +709,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;
@@ -796,8 +796,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);
@@ -1330,7 +1330,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();
             }
@@ -1946,8 +1946,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) {
@@ -2750,10 +2750,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;
             }
@@ -2762,10 +2762,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/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index 292fc14..51cb950 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -165,7 +165,8 @@
 
     @Override
     public String toString() {
-        return "type: " + mDeviceType + "internal type: " + mInternalDeviceType
+        return "type: " + mDeviceType
+                + " internal type: 0x" + Integer.toHexString(mInternalDeviceType)
                 + " addr: " + mDeviceAddress + " bt audio type: "
                 + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory)
                 + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index eea3d38..2336753 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1252,8 +1252,8 @@
     }
 
     /*package*/ void registerStrategyPreferredDevicesDispatcher(
-            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
-        mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher);
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mDeviceInventory.registerStrategyPreferredDevicesDispatcher(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -1262,8 +1262,8 @@
     }
 
     /*package*/ void registerStrategyNonDefaultDevicesDispatcher(
-            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
-        mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mDeviceInventory.registerStrategyNonDefaultDevicesDispatcher(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
@@ -1281,8 +1281,8 @@
     }
 
     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
-            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
-        mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
+        mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -1290,6 +1290,11 @@
         mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher);
     }
 
+    /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked(
+            List<AudioDeviceAttributes> devices) {
+        return mAudioService.anonymizeAudioDeviceAttributesListUnchecked(devices);
+    }
+
     /*package*/ void registerCommunicationDeviceDispatcher(
             @NonNull ICommunicationDeviceDispatcher dispatcher) {
         mCommDevDispatchers.register(dispatcher);
@@ -2684,4 +2689,5 @@
     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 e08fdd6..a1d2e14 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -121,6 +121,26 @@
     }
 
     /**
+     * Adds a new entry in mDeviceInventory if the AudioDeviceAttributes passed is an sink
+     * Bluetooth device and no corresponding entry already exists.
+     * @param ada the device to add if needed
+     */
+    void addAudioDeviceInInventoryIfNeeded(AudioDeviceAttributes ada) {
+        if (!AudioSystem.isBluetoothOutDevice(ada.getInternalType())) {
+            return;
+        }
+        synchronized (mDeviceInventoryLock) {
+            if (findDeviceStateForAudioDeviceAttributes(ada, ada.getType()) != null) {
+                return;
+            }
+            AdiDeviceState ads = new AdiDeviceState(
+                    ada.getType(), ada.getInternalType(), ada.getAddress());
+            mDeviceInventory.put(ads.getDeviceId(), ads);
+        }
+        mDeviceBroker.persistAudioDeviceSettings();
+    }
+
+    /**
      * Adds a new AdiDeviceState or updates the audio device cateogory of the matching
      * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
      * @param deviceState the device to update
@@ -992,8 +1012,8 @@
 
 
     /*package*/ void registerStrategyPreferredDevicesDispatcher(
-            @NonNull IStrategyPreferredDevicesDispatcher dispatcher) {
-        mPrefDevDispatchers.register(dispatcher);
+            @NonNull IStrategyPreferredDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mPrefDevDispatchers.register(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyPreferredDevicesDispatcher(
@@ -1002,8 +1022,8 @@
     }
 
     /*package*/ void registerStrategyNonDefaultDevicesDispatcher(
-            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher) {
-        mNonDefDevDispatchers.register(dispatcher);
+            @NonNull IStrategyNonDefaultDevicesDispatcher dispatcher, boolean isPrivileged) {
+        mNonDefDevDispatchers.register(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterStrategyNonDefaultDevicesDispatcher(
@@ -1084,8 +1104,8 @@
     }
 
     /*package*/ void registerCapturePresetDevicesRoleDispatcher(
-            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) {
-        mDevRoleCapturePresetDispatchers.register(dispatcher);
+            @NonNull ICapturePresetDevicesRoleDispatcher dispatcher, boolean isPrivileged) {
+        mDevRoleCapturePresetDispatchers.register(dispatcher, isPrivileged);
     }
 
     /*package*/ void unregisterCapturePresetDevicesRoleDispatcher(
@@ -1414,6 +1434,8 @@
                     updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
                     if (!connect) {
                         purgeDevicesRoles_l();
+                    } else {
+                        addAudioDeviceInInventoryIfNeeded(attributes);
                     }
                 }
                 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
@@ -1702,6 +1724,7 @@
         setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
 
         updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
+        addAudioDeviceInInventoryIfNeeded(ada);
     }
 
     static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2006,9 +2029,9 @@
         final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID);
         mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
-
-        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_HEARING_AID, address, name),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_HEARING_AID, address, name);
+        mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_AVAILABLE,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
@@ -2018,6 +2041,7 @@
         mDeviceBroker.postApplyVolumeOnDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
         setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
+        addAudioDeviceInInventoryIfNeeded(ada);
         new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
                 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
                 .set(MediaMetrics.Property.DEVICE,
@@ -2128,6 +2152,7 @@
                             sensorUuid));
             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
+            addAudioDeviceInInventoryIfNeeded(ada);
         }
 
         if (streamType == AudioSystem.STREAM_DEFAULT) {
@@ -2462,6 +2487,9 @@
         final int nbDispatchers = mPrefDevDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; i++) {
             try {
+                if (!((Boolean) mPrefDevDispatchers.getBroadcastCookie(i))) {
+                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+                }
                 mPrefDevDispatchers.getBroadcastItem(i).dispatchPrefDevicesChanged(
                         strategy, devices);
             } catch (RemoteException e) {
@@ -2475,6 +2503,9 @@
         final int nbDispatchers = mNonDefDevDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; i++) {
             try {
+                if (!((Boolean) mNonDefDevDispatchers.getBroadcastCookie(i))) {
+                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+                }
                 mNonDefDevDispatchers.getBroadcastItem(i).dispatchNonDefDevicesChanged(
                         strategy, devices);
             } catch (RemoteException e) {
@@ -2488,6 +2519,9 @@
         final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast();
         for (int i = 0; i < nbDispatchers; ++i) {
             try {
+                if (!((Boolean) mDevRoleCapturePresetDispatchers.getBroadcastCookie(i))) {
+                    devices = mDeviceBroker.anonymizeAudioDeviceAttributesListUnchecked(devices);
+                }
                 mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged(
                         capturePreset, role, devices);
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3243385..9b03afb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -36,6 +36,7 @@
 import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
 import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
 import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
+
 import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
 import static com.android.server.utils.EventLogger.Event.ALOGE;
 import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -201,6 +202,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
+import com.android.media.audio.flags.Flags;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -2881,7 +2883,7 @@
     // IPC methods
     ///////////////////////////////////////////////////////////////////////////
     /**
-     * @see AudioManager#setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
+     * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy, AudioDeviceAttributes)
      * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy,
      *                                                  List<AudioDeviceAttributes>)
      */
@@ -2891,8 +2893,11 @@
         if (devices == null) {
             return AudioSystem.ERROR;
         }
+
+        devices = retrieveBluetoothAddresses(devices);
+
         final String logString = String.format(
-                "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
+                "setPreferredDevicesForStrategy u/pid:%d/%d strat:%d dev:%s",
                 Binder.getCallingUid(), Binder.getCallingPid(), strategy,
                 devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -2948,7 +2953,7 @@
                     status, strategy));
             return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices;
+            return anonymizeAudioDeviceAttributesList(devices);
         }
     }
 
@@ -2963,6 +2968,9 @@
                                                 @NonNull AudioDeviceAttributes device) {
         super.setDeviceAsNonDefaultForStrategy_enforcePermission();
         Objects.requireNonNull(device);
+
+        device = retrieveBluetoothAddress(device);
+
         final String logString = String.format(
                 "setDeviceAsNonDefaultForStrategy u/pid:%d/%d strat:%d dev:%s",
                 Binder.getCallingUid(), Binder.getCallingPid(), strategy, device.toString());
@@ -2989,6 +2997,9 @@
                                                    AudioDeviceAttributes device) {
         super.removeDeviceAsNonDefaultForStrategy_enforcePermission();
         Objects.requireNonNull(device);
+
+        device = retrieveBluetoothAddress(device);
+
         final String logString = String.format(
                 "removeDeviceAsNonDefaultForStrategy strat:%d dev:%s", strategy, device.toString());
         sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
@@ -3023,7 +3034,7 @@
                     status, strategy));
             return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices;
+            return anonymizeAudioDeviceAttributesList(devices);
         }
     }
 
@@ -3036,7 +3047,8 @@
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerStrategyPreferredDevicesDispatcher(dispatcher);
+        mDeviceBroker.registerStrategyPreferredDevicesDispatcher(
+                dispatcher, isBluetoothPrividged());
     }
 
     /** @see AudioManager#removeOnPreferredDevicesForStrategyChangedListener(
@@ -3060,7 +3072,8 @@
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(dispatcher);
+        mDeviceBroker.registerStrategyNonDefaultDevicesDispatcher(
+                dispatcher, isBluetoothPrividged());
     }
 
     /** @see AudioManager#removeOnNonDefaultDevicesForStrategyChangedListener(
@@ -3076,7 +3089,7 @@
     }
 
     /**
-     * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)
+     * @see AudioManager#setPreferredDevicesForCapturePreset(int, AudioDeviceAttributes)
      */
     public int setPreferredDevicesForCapturePreset(
             int capturePreset, List<AudioDeviceAttributes> devices) {
@@ -3095,6 +3108,8 @@
             return AudioSystem.ERROR;
         }
 
+        devices = retrieveBluetoothAddresses(devices);
+
         final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync(
                 capturePreset, devices);
         if (status != AudioSystem.SUCCESS) {
@@ -3141,7 +3156,7 @@
                     status, capturePreset));
             return new ArrayList<AudioDeviceAttributes>();
         } else {
-            return devices;
+            return anonymizeAudioDeviceAttributesList(devices);
         }
     }
 
@@ -3155,7 +3170,8 @@
             return;
         }
         enforceModifyAudioRoutingPermission();
-        mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher);
+        mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(
+                dispatcher, isBluetoothPrividged());
     }
 
     /**
@@ -3175,7 +3191,9 @@
     public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
             @NonNull AudioAttributes attributes) {
         enforceQueryStateOrModifyRoutingPermission();
-        return getDevicesForAttributesInt(attributes, false /* forVolume */);
+
+        return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList(
+                getDevicesForAttributesInt(attributes, false /* forVolume */)));
     }
 
     /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes)
@@ -3185,7 +3203,8 @@
      */
     public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected(
             @NonNull AudioAttributes attributes) {
-        return getDevicesForAttributesInt(attributes, false /* forVolume */);
+        return new ArrayList<AudioDeviceAttributes>(anonymizeAudioDeviceAttributesList(
+                getDevicesForAttributesInt(attributes, false /* forVolume */)));
     }
 
     /**
@@ -7390,6 +7409,8 @@
         Objects.requireNonNull(device);
         AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
 
+        device = retrieveBluetoothAddress(device);
+
         sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
                 + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
                 + device.getAddress() + " behavior:"
@@ -7473,6 +7494,8 @@
         // verify parameters
         Objects.requireNonNull(device);
 
+        device = retrieveBluetoothAddress(device);
+
         return getDeviceVolumeBehaviorInt(device);
     }
 
@@ -7547,9 +7570,12 @@
     /**
      * see AudioManager.setWiredDeviceConnectionState()
      */
-    public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes,
+    public void setWiredDeviceConnectionState(@NonNull AudioDeviceAttributes attributes,
             @ConnectionState int state, String caller) {
         super.setWiredDeviceConnectionState_enforcePermission();
+        Objects.requireNonNull(attributes);
+
+        attributes = retrieveBluetoothAddress(attributes);
 
         if (state != CONNECTION_STATE_CONNECTED
                 && state != CONNECTION_STATE_DISCONNECTED) {
@@ -7590,6 +7616,9 @@
             boolean connected) {
         Objects.requireNonNull(device);
         enforceModifyAudioRoutingPermission();
+
+        device = retrieveBluetoothAddress(device);
+
         mDeviceBroker.setTestDeviceConnectionState(device,
                 connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED);
         // simulate a routing update from native
@@ -10422,6 +10451,103 @@
         mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
     }
 
+    private boolean isBluetoothPrividged() {
+        if (!Flags.bluetoothMacAddressAnonymization()) {
+            return true;
+        }
+        return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.BLUETOOTH_CONNECT)
+                || Binder.getCallingUid() == Process.SYSTEM_UID;
+    }
+
+    List<AudioDeviceAttributes> retrieveBluetoothAddresses(List<AudioDeviceAttributes> devices) {
+        if (isBluetoothPrividged()) {
+            return devices;
+        }
+
+        List<AudioDeviceAttributes> checkedDevices = new ArrayList<AudioDeviceAttributes>();
+        for (AudioDeviceAttributes ada : devices) {
+            if (ada == null) {
+                continue;
+            }
+            checkedDevices.add(retrieveBluetoothAddressUncheked(ada));
+        }
+        return checkedDevices;
+    }
+
+    AudioDeviceAttributes retrieveBluetoothAddress(@NonNull AudioDeviceAttributes ada) {
+        if (isBluetoothPrividged()) {
+            return ada;
+        }
+        return retrieveBluetoothAddressUncheked(ada);
+    }
+
+    AudioDeviceAttributes retrieveBluetoothAddressUncheked(@NonNull AudioDeviceAttributes ada) {
+        Objects.requireNonNull(ada);
+        if (AudioSystem.isBluetoothDevice(ada.getInternalType())) {
+            String anonymizedAddress = anonymizeBluetoothAddress(ada.getAddress());
+            for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) {
+                if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType())
+                        && (ada.getInternalType() == ads.getInternalDeviceType())
+                        && anonymizedAddress.equals(anonymizeBluetoothAddress(
+                                ads.getDeviceAddress())))) {
+                    continue;
+                }
+                ada.setAddress(ads.getDeviceAddress());
+                break;
+            }
+        }
+        return ada;
+    }
+
+    /**
+     * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app
+     * Must match the implementation of BluetoothUtils.toAnonymizedAddress()
+     * @param address Mac address to be anonymized
+     * @return anonymized mac address
+     */
+    static String anonymizeBluetoothAddress(String address) {
+        if (address == null || address.length() != "AA:BB:CC:DD:EE:FF".length()) {
+            return null;
+        }
+        return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length());
+    }
+
+    private List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesList(
+                List<AudioDeviceAttributes> devices) {
+        if (isBluetoothPrividged()) {
+            return devices;
+        }
+        return anonymizeAudioDeviceAttributesListUnchecked(devices);
+    }
+
+    /* package */ List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesListUnchecked(
+            List<AudioDeviceAttributes> devices) {
+        List<AudioDeviceAttributes> anonymizedDevices = new ArrayList<AudioDeviceAttributes>();
+        for (AudioDeviceAttributes ada : devices) {
+            anonymizedDevices.add(anonymizeAudioDeviceAttributesUnchecked(ada));
+        }
+        return anonymizedDevices;
+    }
+
+    private AudioDeviceAttributes anonymizeAudioDeviceAttributesUnchecked(
+            AudioDeviceAttributes ada) {
+        if (!AudioSystem.isBluetoothDevice(ada.getInternalType())) {
+            return ada;
+        }
+        AudioDeviceAttributes res = new AudioDeviceAttributes(ada);
+        res.setAddress(anonymizeBluetoothAddress(ada.getAddress()));
+        return res;
+    }
+
+    private AudioDeviceAttributes anonymizeAudioDeviceAttributes(AudioDeviceAttributes ada) {
+        if (isBluetoothPrividged()) {
+            return ada;
+        }
+
+        return anonymizeAudioDeviceAttributesUnchecked(ada);
+    }
+
     //==========================================================================================
 
     // camera sound is forced if any of the resources corresponding to one active SIM
@@ -10469,13 +10595,16 @@
         Objects.requireNonNull(usages);
         Objects.requireNonNull(device);
         enforceModifyAudioRoutingPermission();
+
+        final AudioDeviceAttributes ada = retrieveBluetoothAddress(device);
+
         if (timeOutMs <= 0 || usages.length == 0) {
             throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute");
         }
         Log.i(TAG, "muteAwaitConnection dev:" + device + " timeOutMs:" + timeOutMs
                 + " usages:" + Arrays.toString(usages));
 
-        if (mDeviceBroker.isDeviceConnected(device)) {
+        if (mDeviceBroker.isDeviceConnected(ada)) {
             // not throwing an exception as there could be a race between a connection (server-side,
             // notification of connection in flight) and a mute operation (client-side)
             Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected");
@@ -10487,12 +10616,19 @@
                         + mMutingExpectedDevice);
                 throw new IllegalStateException("muteAwaitConnection already in progress");
             }
-            mMutingExpectedDevice = device;
+            mMutingExpectedDevice = ada;
             mMutedUsagesAwaitingConnection = usages;
-            mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs);
+            mPlaybackMonitor.muteAwaitConnection(usages, ada, timeOutMs);
         }
-        dispatchMuteAwaitConnection(cb -> { try {
-            cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } });
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
+                AudioDeviceAttributes dev = ada;
+                if (!isPrivileged) {
+                    dev = anonymizeAudioDeviceAttributesUnchecked(ada);
+                }
+                cb.dispatchOnMutedUntilConnection(dev, usages);
+            } catch (RemoteException e) { }
+        });
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@@ -10501,7 +10637,7 @@
         super.getMutingExpectedDevice_enforcePermission();
 
         synchronized (mMuteAwaitConnectionLock) {
-            return mMutingExpectedDevice;
+            return anonymizeAudioDeviceAttributes(mMutingExpectedDevice);
         }
     }
 
@@ -10510,6 +10646,9 @@
     public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) {
         Objects.requireNonNull(device);
         enforceModifyAudioRoutingPermission();
+
+        final AudioDeviceAttributes ada = retrieveBluetoothAddress(device);
+
         Log.i(TAG, "cancelMuteAwaitConnection for device:" + device);
         final int[] mutedUsages;
         synchronized (mMuteAwaitConnectionLock) {
@@ -10519,7 +10658,7 @@
                 Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device");
                 return;
             }
-            if (!device.equalTypeAddress(mMutingExpectedDevice)) {
+            if (!ada.equalTypeAddress(mMutingExpectedDevice)) {
                 Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device
                         + "] but expected device is" + mMutingExpectedDevice);
                 throw new IllegalStateException("cancelMuteAwaitConnection for wrong device");
@@ -10529,8 +10668,14 @@
             mMutedUsagesAwaitingConnection = null;
             mPlaybackMonitor.cancelMuteAwaitConnection("cancelMuteAwaitConnection dev:" + device);
         }
-        dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
-                    AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages);
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
+                AudioDeviceAttributes dev = ada;
+                if (!isPrivileged) {
+                    dev = anonymizeAudioDeviceAttributesUnchecked(ada);
+                }
+                cb.dispatchOnUnmutedEvent(
+                        AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, dev, mutedUsages);
             } catch (RemoteException e) { } });
     }
 
@@ -10544,7 +10689,7 @@
         super.registerMuteAwaitConnectionDispatcher_enforcePermission();
 
         if (register) {
-            mMuteAwaitConnectionDispatchers.register(cb);
+            mMuteAwaitConnectionDispatchers.register(cb, isBluetoothPrividged());
         } else {
             mMuteAwaitConnectionDispatchers.unregister(cb);
         }
@@ -10568,8 +10713,14 @@
             mPlaybackMonitor.cancelMuteAwaitConnection(
                     "checkMuteAwaitConnection device " + device + " connected, unmuting");
         }
-        dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
-                AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages);
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
+                AudioDeviceAttributes ada = device;
+                if (!isPrivileged) {
+                    ada = anonymizeAudioDeviceAttributesUnchecked(device);
+                }
+                cb.dispatchOnUnmutedEvent(AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION,
+                        ada, mutedUsages);
             } catch (RemoteException e) { } });
     }
 
@@ -10589,7 +10740,8 @@
             mMutingExpectedDevice = null;
             mMutedUsagesAwaitingConnection = null;
         }
-        dispatchMuteAwaitConnection(cb -> { try {
+        dispatchMuteAwaitConnection((cb, isPrivileged) -> {
+            try {
                 cb.dispatchOnUnmutedEvent(
                         AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT,
                         timedOutDevice, mutedUsages);
@@ -10597,13 +10749,14 @@
     }
 
     private void dispatchMuteAwaitConnection(
-            java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) {
+            java.util.function.BiConsumer<IMuteAwaitConnectionCallback, Boolean> callback) {
         final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast();
         // lazy initialization as errors unlikely
         ArrayList<IMuteAwaitConnectionCallback> errorList = null;
         for (int i = 0; i < nbDispatchers; i++) {
             try {
-                callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
+                callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i),
+                        (Boolean) mMuteAwaitConnectionDispatchers.getBroadcastCookie(i));
             } catch (Exception e) {
                 if (errorList == null) {
                     errorList = new ArrayList<>(1);
@@ -13243,6 +13396,9 @@
             @NonNull AudioDeviceAttributes device, @IntRange(from = 0) long delayMillis) {
         Objects.requireNonNull(device, "device must not be null");
         enforceModifyAudioRoutingPermission();
+
+        device = retrieveBluetoothAddress(device);
+
         final String getterKey = "additional_output_device_delay="
                 + device.getInternalType() + "," + device.getAddress(); // "getter" key as an id.
         final String setterKey = getterKey + "," + delayMillis;     // append the delay for setter
@@ -13263,6 +13419,9 @@
     @IntRange(from = 0)
     public long getAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) {
         Objects.requireNonNull(device, "device must not be null");
+
+        device = retrieveBluetoothAddress(device);
+
         final String key = "additional_output_device_delay";
         final String reply = AudioSystem.getParameters(
                 key + "=" + device.getInternalType() + "," + device.getAddress());
@@ -13290,6 +13449,9 @@
     @IntRange(from = 0)
     public long getMaxAdditionalOutputDeviceDelay(@NonNull AudioDeviceAttributes device) {
         Objects.requireNonNull(device, "device must not be null");
+
+        device = retrieveBluetoothAddress(device);
+
         final String key = "max_additional_output_device_delay";
         final String reply = AudioSystem.getParameters(
                 key + "=" + device.getInternalType() + "," + device.getAddress());
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 64b17e5..84be521 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -588,8 +588,7 @@
             return wakeLockName;
         }
         return (wakeLockName = target.provider
-                + "/" + target.account.type
-                + "/" + target.account.name);
+                + "/" + target.account.type);
     }
 
     // TODO: Test this to make sure that casting to object doesn't lose the type info for EventLog.
diff --git a/services/core/java/com/android/server/feature/Android.bp b/services/core/java/com/android/server/feature/Android.bp
new file mode 100644
index 0000000..067288d
--- /dev/null
+++ b/services/core/java/com/android/server/feature/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+    name: "dropbox_flags",
+    package: "com.android.server.feature.flags",
+    srcs: [
+        "dropbox_flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "dropbox_flags_lib",
+    aconfig_declarations: "dropbox_flags",
+}
diff --git a/services/core/java/com/android/server/feature/dropbox_flags.aconfig b/services/core/java/com/android/server/feature/dropbox_flags.aconfig
new file mode 100644
index 0000000..fee4bf3
--- /dev/null
+++ b/services/core/java/com/android/server/feature/dropbox_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.feature.flags"
+
+flag{
+    name: "enable_read_dropbox_permission"
+    namespace: "preload_safety"
+    description: "Feature flag for permission to Read dropbox data"
+    bug: "287512663"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f35b045..568618e 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -697,9 +697,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;
         }
 
@@ -1030,8 +1030,8 @@
             // 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.
+            // 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);
@@ -1066,7 +1066,7 @@
                 Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
                 return;
             }
-            setUserKeyProtection(userId, result.syntheticPassword);
+            setCeStorageProtection(userId, result.syntheticPassword);
         }
     }
 
@@ -2005,11 +2005,11 @@
         mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext));
     }
 
-    private void setUserKeyProtection(@UserIdInt int userId, SyntheticPassword sp) {
+    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 {
@@ -2017,11 +2017,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;
         }
     }
@@ -2032,8 +2032,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;
         }
@@ -2041,7 +2041,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);
@@ -2054,8 +2054,10 @@
     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 +2074,7 @@
                 return;
             }
             onSyntheticPasswordUnlocked(userId, result.syntheticPassword);
-            unlockUserKey(userId, result.syntheticPassword);
+            unlockCeStorage(userId, result.syntheticPassword);
         }
     }
 
@@ -2775,7 +2777,7 @@
             final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
                     LockscreenCredential.createNone(), sp, userId);
             setCurrentLskfBasedProtectorId(protectorId, userId);
-            setUserKeyProtection(userId, sp);
+            setCeStorageProtection(userId, sp);
             onSyntheticPasswordCreated(userId, sp);
             Slogf.i(TAG, "Successfully initialized synthetic password for user %d", userId);
             return sp;
@@ -2836,7 +2838,7 @@
 
         unlockKeystore(userId, sp);
 
-        unlockUserKey(userId, sp);
+        unlockCeStorage(userId, sp);
 
         unlockUser(userId);
 
@@ -2900,7 +2902,7 @@
 
             mSpManager.clearSidForUser(userId);
             gateKeeperClearSecureUserId(userId);
-            unlockUserKey(userId, sp);
+            unlockCeStorage(userId, sp);
             unlockKeystore(userId, sp);
             setKeystorePassword(null, userId);
             removeBiometricsForUser(userId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7ca5699..4b5d52f 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -244,6 +244,7 @@
 import android.permission.PermissionManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
 import android.service.notification.ConversationChannelWrapper;
@@ -2008,6 +2009,8 @@
                         Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         private final Uri LOCK_SCREEN_SHOW_NOTIFICATIONS
                 = Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
+        private final Uri SHOW_NOTIFICATION_SNOOZE
+                = Settings.Secure.getUriFor(Settings.Secure.SHOW_NOTIFICATION_SNOOZE);
 
         SettingsObserver(Handler handler) {
             super(handler);
@@ -2034,6 +2037,10 @@
                     false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(LOCK_SCREEN_SHOW_NOTIFICATIONS,
                     false, this, UserHandle.USER_ALL);
+
+            resolver.registerContentObserver(SHOW_NOTIFICATION_SNOOZE,
+                    false, this, UserHandle.USER_ALL);
+
             update(null);
         }
 
@@ -2083,6 +2090,14 @@
             if (uri == null || LOCK_SCREEN_SHOW_NOTIFICATIONS.equals(uri)) {
                 mPreferencesHelper.updateLockScreenShowNotifications();
             }
+            if (SHOW_NOTIFICATION_SNOOZE.equals(uri)) {
+                final boolean snoozeEnabled = Settings.Secure.getIntForUser(resolver,
+                        Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT)
+                        != 0;
+                if (!snoozeEnabled) {
+                    unsnoozeAll();
+                }
+            }
         }
     }
 
@@ -7792,6 +7807,13 @@
         }
     }
 
+    private void unsnoozeAll() {
+        synchronized (mNotificationLock) {
+            mSnoozeHelper.repostAll(mUserProfiles.getCurrentProfileIds());
+            handleSavePolicyFile();
+        }
+    }
+
     protected class CancelNotificationRunnable implements Runnable {
         private final int mCallingUid;
         private final int mCallingPid;
diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS
index 72c4529..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: 1305560
+# 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/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 0176989..8f5676b3 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -296,6 +296,20 @@
         }
     }
 
+    /**
+     * Unsnooze & repost all snoozed notifications for userId and its profiles
+     */
+    protected void repostAll(IntArray userIds) {
+        synchronized (mLock) {
+            List<NotificationRecord> snoozedNotifications = getSnoozed();
+            for (NotificationRecord r : snoozedNotifications) {
+                if (userIds.binarySearch(r.getUserId()) >= 0) {
+                    repost(r.getKey(), r.getUserId(), false);
+                }
+            }
+        }
+    }
+
     protected void repost(String key, boolean muteOnReturn) {
         synchronized (mLock) {
             final NotificationRecord r = mSnoozedNotifications.get(key);
diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java
index e53c436..ca149c5 100644
--- a/services/core/java/com/android/server/os/SchedulingPolicyService.java
+++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java
@@ -219,6 +219,7 @@
         case Process.AUDIOSERVER_UID:  // fastcapture, fastmixer
         case Process.CAMERASERVER_UID: // camera high frame rate recording
         case Process.BLUETOOTH_UID:    // Bluetooth audio playback
+        case Process.PHONE_UID:        // phone call
             return true;
         default:
             return false;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7cac870..7d716a68 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2119,24 +2119,6 @@
                             // ignore; not possible for non-system app
                         }
                     }
-                    // Successfully deleted the old package; proceed with replace.
-                    // Update the in-memory copy of the previous code paths.
-                    PackageSetting ps1 = mPm.mSettings.getPackageLPr(
-                            installRequest.getExistingPackageName());
-                    if ((installRequest.getInstallFlags() & PackageManager.DONT_KILL_APP)
-                            == 0) {
-                        Set<String> oldCodePaths = ps1.getOldCodePaths();
-                        if (oldCodePaths == null) {
-                            oldCodePaths = new ArraySet<>();
-                        }
-                        if (oldPackage != null) {
-                            Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath());
-                            Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths());
-                        }
-                        ps1.setOldCodePaths(oldCodePaths);
-                    } else {
-                        ps1.setOldCodePaths(null);
-                    }
 
                     if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
                         PackageSetting ps2 = mPm.mSettings.getPackageLPr(
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 4922851..42a97f7 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -29,6 +29,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.content.Context;
@@ -214,10 +215,13 @@
     ArchiveState createArchiveStateInternal(String packageName, int userId,
             List<LauncherActivityInfo> mainActivities, String installerPackage)
             throws IOException {
+        final int iconSize = mContext.getSystemService(
+                ActivityManager.class).getLauncherLargeIconSize();
+
         List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
         for (int i = 0, size = mainActivities.size(); i < size; i++) {
             LauncherActivityInfo mainActivity = mainActivities.get(i);
-            Path iconPath = storeIcon(packageName, mainActivity, userId, i);
+            Path iconPath = storeIcon(packageName, mainActivity, userId, i, iconSize);
             ArchiveActivityInfo activityInfo =
                     new ArchiveActivityInfo(
                             mainActivity.getLabel().toString(),
@@ -247,7 +251,7 @@
 
     @VisibleForTesting
     Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
-            @UserIdInt int userId, int index) throws IOException {
+            @UserIdInt int userId, int index, int iconSize) throws IOException {
         int iconResourceId = mainActivity.getActivityInfo().getIconResource();
         if (iconResourceId == 0) {
             // The app doesn't define an icon. No need to store anything.
@@ -255,7 +259,7 @@
         }
         File iconsDir = createIconsDir(userId);
         File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
-        Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0));
+        Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0), iconSize);
         try (FileOutputStream out = new FileOutputStream(iconFile)) {
             // Note: Quality is ignored for PNGs.
             if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) {
@@ -590,8 +594,8 @@
     /**
      * Creates serializable archived activities from launcher activities.
      */
-    static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos)
-            throws IOException {
+    static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos,
+            int iconSize) throws IOException {
         if (infos == null || infos.isEmpty()) {
             throw new IllegalArgumentException("No launcher activities");
         }
@@ -606,7 +610,7 @@
             archivedActivity.title = info.getLabel().toString();
             archivedActivity.originalComponentName = info.getComponentName();
             archivedActivity.iconBitmap = info.getActivityInfo().getIconResource() == 0 ? null :
-                    bytesFromBitmap(drawableToBitmap(info.getIcon(/* density= */ 0)));
+                    bytesFromBitmap(drawableToBitmap(info.getIcon(/* density= */ 0), iconSize));
             // TODO(b/298452477) Handle monochrome icons.
             archivedActivity.monochromeIconBitmap = null;
             activities.add(archivedActivity);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 61b6b24..52655c4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1486,11 +1486,14 @@
                 archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
                         archiveState);
             } else {
+                final int iconSize = mContext.getSystemService(
+                        ActivityManager.class).getLauncherLargeIconSize();
+
                 var mainActivities =
                         mInstallerService.mPackageArchiver.getLauncherActivityInfos(packageName,
                                 userId);
                 archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
-                        mainActivities);
+                        mainActivities, iconSize);
             }
         } catch (Exception e) {
             throw new IllegalArgumentException("Package does not have a main activity", e);
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index ad26d1f..3cf5481 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -121,10 +121,6 @@
     @Nullable
     private Map<String, Set<String>> mimeGroups;
 
-    @Deprecated
-    @Nullable
-    private Set<String> mOldCodePaths;
-
     @Nullable
     private String[] usesSdkLibraries;
 
@@ -722,15 +718,6 @@
             }
         }
 
-        if (mOldCodePaths != null) {
-            if (other.mOldCodePaths != null) {
-                mOldCodePaths.clear();
-                mOldCodePaths.addAll(other.mOldCodePaths);
-            } else {
-                mOldCodePaths = null;
-            }
-        }
-
         copyMimeGroups(other.mimeGroups);
         pkgState.updateFrom(other.pkgState);
         onChanged();
@@ -1428,12 +1415,6 @@
         return this;
     }
 
-    public PackageSetting setOldCodePaths(Set<String> oldCodePaths) {
-        mOldCodePaths = oldCodePaths;
-        onChanged();
-        return this;
-    }
-
     public PackageSetting setUsesSdkLibraries(String[] usesSdkLibraries) {
         this.usesSdkLibraries = usesSdkLibraries;
         onChanged();
@@ -1593,11 +1574,6 @@
     //@formatter:off
 
 
-    @DataClass.Generated.Member
-    public @Deprecated @Nullable Set<String> getOldCodePaths() {
-        return mOldCodePaths;
-    }
-
     /**
      * The path under which native libraries have been unpacked. This path is
      * always derived at runtime, and is only stored here for cleanup when a
@@ -1731,10 +1707,10 @@
     }
 
     @DataClass.Generated(
-            time = 1698097434269L,
+            time = 1698188444364L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 154ee6e..bb55a39 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5095,9 +5095,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
@@ -5899,17 +5899,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
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 88c2e09..dd39fb0 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -34,7 +34,7 @@
 import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.StatsEvent;
 
 import com.android.internal.annotations.GuardedBy;
@@ -256,10 +256,11 @@
 
     @VisibleForTesting
     final class MyUidObserver extends UidObserver {
-        private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
-
+        private final Object mCacheLock = new Object();
+        @GuardedBy("mCacheLock")
+        private final SparseIntArray mProcStatesCache = new SparseIntArray();
         public boolean isUidForeground(int uid) {
-            synchronized (mLock) {
+            synchronized (mCacheLock) {
                 return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
                         <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
             }
@@ -268,6 +269,9 @@
         @Override
         public void onUidGone(int uid, boolean disabled) {
             FgThread.getHandler().post(() -> {
+                synchronized (mCacheLock) {
+                    mProcStatesCache.delete(uid);
+                }
                 synchronized (mLock) {
                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
                     if (tokenMap == null) {
@@ -280,7 +284,6 @@
                             sessionSet.valueAt(j).close();
                         }
                     }
-                    mProcStatesCache.delete(uid);
                 }
             });
         }
@@ -292,15 +295,18 @@
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
             FgThread.getHandler().post(() -> {
-                synchronized (mLock) {
+                synchronized (mCacheLock) {
                     mProcStatesCache.put(uid, procState);
+                }
+                boolean shouldAllowUpdate = isUidForeground(uid);
+                synchronized (mLock) {
                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(uid);
                     if (tokenMap == null) {
                         return;
                     }
                     for (ArraySet<AppHintSession> sessionSet : tokenMap.values()) {
                         for (AppHintSession s : sessionSet) {
-                            s.onProcStateChanged();
+                            s.onProcStateChanged(shouldAllowUpdate);
                         }
                     }
                 }
@@ -429,10 +435,10 @@
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
                 return;
             }
+            pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
+            pw.println("HAL Support: " + isHalSupported());
+            pw.println("Active Sessions:");
             synchronized (mLock) {
-                pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
-                pw.println("HAL Support: " + isHalSupported());
-                pw.println("Active Sessions:");
                 for (int i = 0; i < mActiveSessions.size(); i++) {
                     pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
                     ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
@@ -476,7 +482,8 @@
             mHalSessionPtr = halSessionPtr;
             mTargetDurationNanos = durationNanos;
             mUpdateAllowed = true;
-            updateHintAllowed();
+            final boolean allowed = mUidObserver.isUidForeground(mUid);
+            updateHintAllowed(allowed);
             try {
                 token.linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -486,9 +493,8 @@
         }
 
         @VisibleForTesting
-        boolean updateHintAllowed() {
-            synchronized (mLock) {
-                final boolean allowed = mUidObserver.isUidForeground(mUid);
+        boolean updateHintAllowed(boolean allowed) {
+            synchronized (this) {
                 if (allowed && !mUpdateAllowed) resume();
                 if (!allowed && mUpdateAllowed) pause();
                 mUpdateAllowed = allowed;
@@ -498,8 +504,8 @@
 
         @Override
         public void updateTargetWorkDuration(long targetDurationNanos) {
-            synchronized (mLock) {
-                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+            synchronized (this) {
+                if (mHalSessionPtr == 0 || !mUpdateAllowed) {
                     return;
                 }
                 Preconditions.checkArgument(targetDurationNanos > 0, "Expected"
@@ -511,8 +517,8 @@
 
         @Override
         public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) {
-            synchronized (mLock) {
-                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+            synchronized (this) {
+                if (mHalSessionPtr == 0 || !mUpdateAllowed) {
                     return;
                 }
                 Preconditions.checkArgument(actualDurationNanos.length != 0, "the count"
@@ -534,11 +540,13 @@
         /** TODO: consider monitor session threads and close session if any thread is dead. */
         @Override
         public void close() {
-            synchronized (mLock) {
+            synchronized (this) {
                 if (mHalSessionPtr == 0) return;
                 mNativeWrapper.halCloseHintSession(mHalSessionPtr);
                 mHalSessionPtr = 0;
                 mToken.unlinkToDeath(this, 0);
+            }
+            synchronized (mLock) {
                 ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = mActiveSessions.get(mUid);
                 if (tokenMap == null) {
                     Slogf.w(TAG, "UID %d is not present in active session map", mUid);
@@ -557,8 +565,8 @@
 
         @Override
         public void sendHint(@PerformanceHintManager.Session.Hint int hint) {
-            synchronized (mLock) {
-                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+            synchronized (this) {
+                if (mHalSessionPtr == 0 || !mUpdateAllowed) {
                     return;
                 }
                 Preconditions.checkArgument(hint >= 0, "the hint ID value should be"
@@ -568,7 +576,7 @@
         }
 
         public void setThreads(@NonNull int[] tids) {
-            synchronized (mLock) {
+            synchronized (this) {
                 if (mHalSessionPtr == 0) {
                     return;
                 }
@@ -588,7 +596,7 @@
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
-                if (!updateHintAllowed()) {
+                if (!mUpdateAllowed) {
                     Slogf.v(TAG, "update hint not allowed, storing tids.");
                     mNewThreadIds = tids;
                     return;
@@ -599,13 +607,15 @@
         }
 
         public int[] getThreadIds() {
-            return mThreadIds;
+            synchronized (this) {
+                return Arrays.copyOf(mThreadIds, mThreadIds.length);
+            }
         }
 
         @Override
         public void setMode(int mode, boolean enabled) {
-            synchronized (mLock) {
-                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+            synchronized (this) {
+                if (mHalSessionPtr == 0 || !mUpdateAllowed) {
                     return;
                 }
                 Preconditions.checkArgument(mode >= 0, "the mode Id value should be"
@@ -614,19 +624,19 @@
             }
         }
 
-        private void onProcStateChanged() {
-            updateHintAllowed();
+        private void onProcStateChanged(boolean updateAllowed) {
+            updateHintAllowed(updateAllowed);
         }
 
         private void pause() {
-            synchronized (mLock) {
+            synchronized (this) {
                 if (mHalSessionPtr == 0) return;
                 mNativeWrapper.halPauseHintSession(mHalSessionPtr);
             }
         }
 
         private void resume() {
-            synchronized (mLock) {
+            synchronized (this) {
                 if (mHalSessionPtr == 0) return;
                 mNativeWrapper.halResumeHintSession(mHalSessionPtr);
                 if (mNewThreadIds != null) {
@@ -638,12 +648,12 @@
         }
 
         private void dump(PrintWriter pw, String prefix) {
-            synchronized (mLock) {
+            synchronized (this) {
                 pw.println(prefix + "SessionPID: " + mPid);
                 pw.println(prefix + "SessionUID: " + mUid);
                 pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds));
                 pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos);
-                pw.println(prefix + "SessionAllowed: " + updateHintAllowed());
+                pw.println(prefix + "SessionAllowed: " + mUpdateAllowed);
             }
         }
 
diff --git a/services/core/java/com/android/server/power/hint/TEST_MAPPING b/services/core/java/com/android/server/power/hint/TEST_MAPPING
new file mode 100644
index 0000000..10c5362
--- /dev/null
+++ b/services/core/java/com/android/server/power/hint/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "postsubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.power.hint"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fd8f6bf..bdab4d4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -9317,8 +9317,9 @@
         final Task rootTask = getRootTask();
         final float minAspectRatio = getMinAspectRatio();
         final TaskFragment organizedTf = getOrganizedTaskFragment();
+        float aspectRatioToApply = desiredAspectRatio;
         if (task == null || rootTask == null
-                || (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1)
+                || (maxAspectRatio < 1 && minAspectRatio < 1 && aspectRatioToApply < 1)
                 // Don't set aspect ratio if we are in VR mode.
                 || isInVrUiMode(getConfiguration())
                 // TODO(b/232898850): Always respect aspect ratio requests.
@@ -9331,30 +9332,30 @@
         final int containingAppHeight = containingAppBounds.height();
         final float containingRatio = computeAspectRatio(containingAppBounds);
 
-        if (desiredAspectRatio < 1) {
-            desiredAspectRatio = containingRatio;
+        if (aspectRatioToApply < 1) {
+            aspectRatioToApply = containingRatio;
         }
 
-        if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
-            desiredAspectRatio = maxAspectRatio;
-        } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
-            desiredAspectRatio = minAspectRatio;
+        if (maxAspectRatio >= 1 && aspectRatioToApply > maxAspectRatio) {
+            aspectRatioToApply = maxAspectRatio;
+        } else if (minAspectRatio >= 1 && aspectRatioToApply < minAspectRatio) {
+            aspectRatioToApply = minAspectRatio;
         }
 
         int activityWidth = containingAppWidth;
         int activityHeight = containingAppHeight;
 
-        if (containingRatio - desiredAspectRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
+        if (containingRatio - aspectRatioToApply > ASPECT_RATIO_ROUNDING_TOLERANCE) {
             if (containingAppWidth < containingAppHeight) {
                 // Width is the shorter side, so we use that to figure-out what the max. height
                 // should be given the aspect ratio.
-                activityHeight = (int) ((activityWidth * desiredAspectRatio) + 0.5f);
+                activityHeight = (int) ((activityWidth * aspectRatioToApply) + 0.5f);
             } else {
                 // Height is the shorter side, so we use that to figure-out what the max. width
                 // should be given the aspect ratio.
-                activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f);
+                activityWidth = (int) ((activityHeight * aspectRatioToApply) + 0.5f);
             }
-        } else if (desiredAspectRatio - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
+        } else if (aspectRatioToApply - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
             boolean adjustWidth;
             switch (getRequestedConfigurationOrientation()) {
                 case ORIENTATION_LANDSCAPE:
@@ -9382,9 +9383,9 @@
                     break;
             }
             if (adjustWidth) {
-                activityWidth = (int) ((activityHeight / desiredAspectRatio) + 0.5f);
+                activityWidth = (int) ((activityHeight / aspectRatioToApply) + 0.5f);
             } else {
-                activityHeight = (int) ((activityWidth / desiredAspectRatio) + 0.5f);
+                activityHeight = (int) ((activityWidth / aspectRatioToApply) + 0.5f);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4fa6e29..e7893da 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1622,7 +1622,17 @@
             return;
         }
 
+        final Transition.ReadyCondition displayConfig = mTransitionController.isCollecting()
+                ? new Transition.ReadyCondition("displayConfig", this) : null;
+        if (displayConfig != null) {
+            mTransitionController.waitFor(displayConfig);
+        } else if (mTransitionController.isShellTransitionsEnabled()) {
+            Slog.e(TAG, "Display reconfigured outside of a transition: " + this);
+        }
         final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
+        if (displayConfig != null) {
+            displayConfig.meet();
+        }
         if (configUpdated) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 1a319ad..fa2c94a 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -87,6 +87,7 @@
     private RootWindowContainer mRootWindowContainer;
     private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
     private boolean mWaitingForWakeTransition;
+    private Transition.ReadyCondition mWaitAodHide = null;
 
     KeyguardController(ActivityTaskManagerService service,
             ActivityTaskSupervisor taskSupervisor) {
@@ -565,8 +566,14 @@
 
     // Defer transition until AOD dismissed.
     void updateDeferTransitionForAod(boolean waiting) {
-        if (waiting == mWaitingForWakeTransition) {
-            return;
+        if (mService.getTransitionController().useFullReadyTracking()) {
+            if (waiting == (mWaitAodHide != null)) {
+                return;
+            }
+        } else {
+            if (waiting == mWaitingForWakeTransition) {
+                return;
+            }
         }
         if (!mService.getTransitionController().isCollecting()) {
             return;
@@ -575,12 +582,17 @@
         if (waiting && isAodShowing(DEFAULT_DISPLAY)) {
             mWaitingForWakeTransition = true;
             mWindowManager.mAtmService.getTransitionController().deferTransitionReady();
+            mWaitAodHide = new Transition.ReadyCondition("AOD hidden");
+            mWindowManager.mAtmService.getTransitionController().waitFor(mWaitAodHide);
             mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS);
         } else if (!waiting) {
             // dismiss the deferring if the AOD state change or cancel awake.
             mWaitingForWakeTransition = false;
             mWindowManager.mAtmService.getTransitionController().continueTransitionReady();
             mWindowManager.mH.removeCallbacks(mResetWaitTransition);
+            final Transition.ReadyCondition waitAodHide = mWaitAodHide;
+            mWaitAodHide = null;
+            waitAodHide.meet();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2281395..c81105a 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2051,6 +2051,8 @@
         }
 
         transitionController.deferTransitionReady();
+        Transition.ReadyCondition pipChangesApplied = new Transition.ReadyCondition("movedToPip");
+        transitionController.waitFor(pipChangesApplied);
         mService.deferWindowLayout();
         try {
             // This will change the root pinned task's windowing mode to its original mode, ensuring
@@ -2235,6 +2237,7 @@
                 }
             } finally {
                 transitionController.continueTransitionReady();
+                pipChangesApplied.meet();
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6c31e3e..d2f6d16 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4715,7 +4715,7 @@
         }
         if (isAttached()) {
             setWindowingMode(WINDOWING_MODE_UNDEFINED);
-            moveTaskToBackInner(this);
+            moveTaskToBackInner(this, null /* transition */);
         }
         if (top.isAttached()) {
             top.setWindowingMode(WINDOWING_MODE_UNDEFINED);
@@ -5718,24 +5718,27 @@
                         mTransitionController.requestStartTransition(transition, tr,
                                 null /* remoteTransition */, null /* displayChange */);
                         mTransitionController.collect(tr);
-                        moveTaskToBackInner(tr);
+                        moveTaskToBackInner(tr, transition);
                     });
         } else {
             // Skip the transition for pinned task.
             if (!inPinnedWindowingMode()) {
                 mDisplayContent.prepareAppTransition(TRANSIT_TO_BACK);
             }
-            moveTaskToBackInner(tr);
+            moveTaskToBackInner(tr, null /* transition */);
         }
         return true;
     }
 
-    private boolean moveTaskToBackInner(@NonNull Task task) {
-        if (mTransitionController.isShellTransitionsEnabled()) {
+    private boolean moveTaskToBackInner(@NonNull Task task, @Nullable Transition transition) {
+        final Transition.ReadyCondition movedToBack =
+                new Transition.ReadyCondition("moved-to-back", task);
+        if (transition != null) {
             // Preventing from update surface position for WindowState if configuration changed,
             // because the position is depends on WindowFrame, so update the position before
             // relayout will only update it to "old" position.
             mAtmService.deferWindowLayout();
+            transition.mReadyTracker.add(movedToBack);
         }
         try {
             moveToBack("moveTaskToBackInner", task);
@@ -5752,6 +5755,9 @@
             if (mTransitionController.isShellTransitionsEnabled()) {
                 mAtmService.continueWindowLayout();
             }
+            if (transition != null) {
+                movedToBack.meet();
+            }
         }
         ActivityRecord topActivity = getDisplayArea().topRunningActivity();
         Task topRootTask = topActivity == null ? null : topActivity.getRootTask();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 7d65c61..f6b4972 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -236,7 +236,8 @@
     private IRemoteCallback mClientAnimationFinishCallback = null;
 
     private @TransitionState int mState = STATE_PENDING;
-    private final ReadyTracker mReadyTracker = new ReadyTracker();
+    private final ReadyTrackerOld mReadyTrackerOld = new ReadyTrackerOld();
+    final ReadyTracker mReadyTracker = new ReadyTracker(this);
 
     private int mRecentsDisplayId = INVALID_DISPLAY;
 
@@ -656,7 +657,7 @@
             updateTransientFlags(info);
             mChanges.put(curr, info);
             if (isReadyGroup(curr)) {
-                mReadyTracker.addGroup(curr);
+                mReadyTrackerOld.addGroup(curr);
                 ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
                                 + " Transition %d with root=%s", mSyncId, curr);
             }
@@ -887,13 +888,18 @@
      */
     void setReady(WindowContainer wc, boolean ready) {
         if (!isCollecting() || mSyncId < 0) return;
-        mReadyTracker.setReadyFrom(wc, ready);
+        mReadyTrackerOld.setReadyFrom(wc, ready);
         applyReady();
     }
 
     private void applyReady() {
         if (mState < STATE_STARTED) return;
-        final boolean ready = mReadyTracker.allReady();
+        final boolean ready;
+        if (mController.useFullReadyTracking()) {
+            ready = mReadyTracker.isReady();
+        } else {
+            ready = mReadyTrackerOld.allReady();
+        }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                 "Set transition ready=%b %d", ready, mSyncId);
         boolean changed = mSyncEngine.setReady(mSyncId, ready);
@@ -909,22 +915,22 @@
 
     /**
      * Sets all possible ready groups to ready.
-     * @see ReadyTracker#setAllReady.
+     * @see ReadyTrackerOld#setAllReady
      */
     void setAllReady() {
         if (!isCollecting() || mSyncId < 0) return;
-        mReadyTracker.setAllReady();
+        mReadyTrackerOld.setAllReady();
         applyReady();
     }
 
     @VisibleForTesting
     boolean allReady() {
-        return mReadyTracker.allReady();
+        return mReadyTrackerOld.allReady();
     }
 
     /** This transition has all of its expected participants. */
     boolean isPopulated() {
-        return mState >= STATE_STARTED && mReadyTracker.allReady();
+        return mState >= STATE_STARTED && mReadyTrackerOld.allReady();
     }
 
     /**
@@ -1438,6 +1444,13 @@
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d",
                 mSyncId);
         mForcePlaying = true;
+        // backwards since conditions are removed.
+        for (int i = mReadyTracker.mConditions.size() - 1; i >= 0; --i) {
+            mReadyTracker.mConditions.get(i).meetAlternate("play-now");
+        }
+        final ReadyCondition forcePlay = new ReadyCondition("force-play-now");
+        mReadyTracker.add(forcePlay);
+        forcePlay.meet();
         setAllReady();
         if (mState == STATE_COLLECTING) {
             start();
@@ -1483,6 +1496,21 @@
             return;
         }
 
+        if (mController.useFullReadyTracking()) {
+            if (mReadyTracker.mMet.isEmpty()) {
+                Slog.e(TAG, "#" + mSyncId + ": No conditions provided");
+            } else {
+                for (int i = 0; i < mReadyTracker.mConditions.size(); ++i) {
+                    Slog.e(TAG, "#" + mSyncId + ": unmet condition at ready: "
+                            + mReadyTracker.mConditions.get(i));
+                }
+            }
+            for (int i = 0; i < mReadyTracker.mMet.size(); ++i) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s",
+                        mSyncId, mReadyTracker.mMet.get(i));
+            }
+        }
+
         // Commit the visibility of visible activities before calculateTransitionInfo(), so the
         // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
         // ActivityRecord#canShowWindows() may reject to show its window. The visibility also
@@ -2797,7 +2825,7 @@
             // if an activity is pausing, it will call setReady(ar, false) and wait for the next
             // resumed activity. Then do not set to ready because the transition only contains
             // partial participants. Otherwise the transition may only handle HIDE and miss OPEN.
-            if (!mReadyTracker.mUsed) {
+            if (!mReadyTrackerOld.mUsed) {
                 setReady(dc, true);
             }
         }
@@ -3059,19 +3087,160 @@
      * {@link #continueTransitionReady}
      */
     void deferTransitionReady() {
-        ++mReadyTracker.mDeferReadyDepth;
+        ++mReadyTrackerOld.mDeferReadyDepth;
         // Make sure it wait until #continueTransitionReady() is called.
         mSyncEngine.setReady(mSyncId, false);
     }
 
     /** This undoes one call to {@link #deferTransitionReady}. */
     void continueTransitionReady() {
-        --mReadyTracker.mDeferReadyDepth;
+        --mReadyTrackerOld.mDeferReadyDepth;
         // Apply ready in case it is waiting for the previous defer call.
         applyReady();
     }
 
     /**
+     * Represents a condition that must be met before an associated transition can be considered
+     * ready.
+     *
+     * Expected usage is that a ReadyCondition is created and then attached to a transition's
+     * ReadyTracker via {@link ReadyTracker#add}. After that, it is expected to monitor the state
+     * of the system and when the condition it represents is met, it will call
+     * {@link ReadyTracker#meet}.
+     *
+     * This base class is a simple explicit, named condition. A caller will create/attach the
+     * condition and then explicitly call {@link #meet} on it (which internally calls
+     * {@link ReadyTracker#meet}.
+     *
+     * Example:
+     * <pre>
+     *     ReadyCondition myCondition = new ReadyCondition("my condition");
+     *     transitionController.waitFor(myCondition);
+     *     ... Some operations ...
+     *     myCondition.meet();
+     * </pre>
+     */
+    static class ReadyCondition {
+        final String mName;
+
+        /** Just used for debugging */
+        final Object mDebugTarget;
+        ReadyTracker mTracker;
+        boolean mMet = false;
+
+        /** If set (non-null), then this is met by another reason besides state (eg. timeout). */
+        String mAlternate = null;
+
+        ReadyCondition(@NonNull String name) {
+            mName = name;
+            mDebugTarget = null;
+        }
+
+        ReadyCondition(@NonNull String name, @Nullable Object debugTarget) {
+            mName = name;
+            mDebugTarget = debugTarget;
+        }
+
+        protected String getDebugRep() {
+            if (mDebugTarget != null) {
+                return mName + ":" + mDebugTarget;
+            }
+            return mName;
+        }
+
+        @Override
+        public String toString() {
+            return "{" + getDebugRep() + (mAlternate != null ? " (" + mAlternate + ")" : "") + "}";
+        }
+
+        /**
+         * Instructs this condition to start tracking system state to detect when this is met.
+         * Don't call this directly; it is called when this object is attached to a transition's
+         * ready-tracker.
+         */
+        void startTracking() {
+        }
+
+        /**
+         * Immediately consider this condition met by an alternative reason (one which doesn't
+         * match the normal intent of this condition -- eg. a timeout).
+         */
+        void meetAlternate(@NonNull String reason) {
+            if (mMet) return;
+            mAlternate = reason;
+            meet();
+        }
+
+        /** Immediately consider this condition met. */
+        void meet() {
+            if (mMet) return;
+            if (mTracker == null) {
+                throw new IllegalStateException("Can't meet a condition before it is waited on");
+            }
+            mTracker.meet(this);
+        }
+    }
+
+    static class ReadyTracker {
+        /**
+         * Used as a place-holder in situations where the transition system isn't active (such as
+         * early-boot, mid shell crash/recovery, or when using legacy).
+         */
+        static final ReadyTracker NULL_TRACKER = new ReadyTracker(null);
+
+        private final Transition mTransition;
+
+        /** List of conditions that are still being waited on. */
+        final ArrayList<ReadyCondition> mConditions = new ArrayList<>();
+
+        /** List of already-met conditions. Fully-qualified for debugging. */
+        final ArrayList<ReadyCondition> mMet = new ArrayList<>();
+
+        ReadyTracker(Transition transition) {
+            mTransition = transition;
+        }
+
+        void add(@NonNull ReadyCondition condition) {
+            if (mTransition == null || !mTransition.mController.useFullReadyTracking()) {
+                condition.mTracker = NULL_TRACKER;
+                return;
+            }
+            mConditions.add(condition);
+            condition.mTracker = this;
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Add condition %s for #%d",
+                    condition, mTransition.mSyncId);
+            condition.startTracking();
+        }
+
+        void meet(@NonNull ReadyCondition condition) {
+            if (mTransition == null || !mTransition.mController.useFullReadyTracking()) return;
+            if (mTransition.mState >= STATE_PLAYING) {
+                Slog.w(TAG, "#%d: Condition met too late, already in state=" + mTransition.mState
+                        + ": " + condition);
+                return;
+            }
+            if (!mConditions.remove(condition)) {
+                if (mMet.contains(condition)) {
+                    throw new IllegalStateException("Can't meet the same condition more than once: "
+                            + condition + " #" + mTransition.mSyncId);
+                } else {
+                    throw new IllegalArgumentException("Can't meet a condition that isn't being "
+                            + "waited on: " + condition + " in #" + mTransition.mSyncId);
+                }
+            }
+            condition.mMet = true;
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Met condition %s for #%d (%d"
+                    + " left)", condition, mTransition.mSyncId, mConditions.size());
+            mMet.add(condition);
+            mTransition.applyReady();
+        }
+
+        boolean isReady() {
+            return mConditions.isEmpty() && !mMet.isEmpty();
+        }
+    }
+
+    /**
      * The transition sync mechanism has 2 parts:
      *   1. Whether all WM operations for a particular transition are "ready" (eg. did the app
      *      launch or stop or get a new configuration?).
@@ -3084,7 +3253,7 @@
      * of readiness across the multiple groups. Currently, we assume that each display is a group
      * since that is how it has been until now.
      */
-    private static class ReadyTracker {
+    private static class ReadyTrackerOld {
         private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>();
 
         /**
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 8ac21e4..28c24c8 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -62,6 +62,7 @@
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.FgThread;
+import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.function.Consumer;
@@ -131,6 +132,7 @@
     TransitionTracer mTransitionTracer;
 
     private SystemPerformanceHinter mSystemPerformanceHinter;
+    private boolean mFullReadyTracking = false;
 
     private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
             new ArrayList<>();
@@ -281,6 +283,7 @@
         registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
         setSyncEngine(wms.mSyncEngine);
         setSystemPerformanceHinter(wms.mSystemPerformanceHinter);
+        mFullReadyTracking = Flags.transitReadyTracking();
     }
 
     @VisibleForTesting
@@ -397,6 +400,14 @@
         return isShellTransitionsEnabled() && SHELL_TRANSITIONS_ROTATION;
     }
 
+    boolean useFullReadyTracking() {
+        return mFullReadyTracking;
+    }
+
+    void setFullReadyTrackingForTest(boolean enabled) {
+        mFullReadyTracking = enabled;
+    }
+
     /**
      * @return {@code true} if transition is actively collecting changes. This is {@code false}
      * once a transition is playing
@@ -1475,6 +1486,19 @@
         applySync.accept(false /* deferred */);
     }
 
+    /**
+     * Instructs the collecting transition to wait until `condition` is met before it is
+     * considered ready.
+     */
+    void waitFor(@NonNull Transition.ReadyCondition condition) {
+        if (mCollectingTransition == null) {
+            Slog.e(TAG, "No collecting transition available to wait for " + condition);
+            condition.mTracker = Transition.ReadyTracker.NULL_TRACKER;
+            return;
+        }
+        mCollectingTransition.mReadyTracker.add(condition);
+    }
+
     interface OnStartCollect {
         void onCollectStarted(boolean deferred);
     }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 5ed6caf..eb60aab 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -305,9 +305,12 @@
                     // This is a direct call from shell, so the entire transition lifecycle is
                     // contained in the provided transaction if provided. Thus, we can setReady
                     // immediately after apply.
+                    final Transition.ReadyCondition wctApplied =
+                            new Transition.ReadyCondition("start WCT applied");
                     final boolean needsSetReady = t != null;
                     final Transition nextTransition = new Transition(type, 0 /* flags */,
                             mTransitionController, mService.mWindowManager.mSyncEngine);
+                    nextTransition.mReadyTracker.add(wctApplied);
                     nextTransition.calcParallelCollectType(wct);
                     mTransitionController.startCollectOrQueue(nextTransition,
                             (deferred) -> {
@@ -315,6 +318,7 @@
                                 nextTransition.mLogger.mStartWCT = wct;
                                 applyTransaction(wct, -1 /* syncId */, nextTransition, caller,
                                         deferred);
+                                wctApplied.meet();
                                 if (needsSetReady) {
                                     // TODO(b/294925498): Remove this once we have accurate ready
                                     //                    tracking.
@@ -329,6 +333,15 @@
                             });
                     return nextTransition.getToken();
                 }
+                // Currently, application of wct can span multiple looper loops (ie.
+                // waitAsyncStart), so add a condition to ensure that it finishes applying.
+                final Transition.ReadyCondition wctApplied;
+                if (t != null) {
+                    wctApplied = new Transition.ReadyCondition("start WCT applied");
+                    transition.mReadyTracker.add(wctApplied);
+                } else {
+                    wctApplied = null;
+                }
                 // The transition already started collecting before sending a request to shell,
                 // so just start here.
                 if (!transition.isCollecting() && !transition.isForcePlaying()) {
@@ -336,6 +349,9 @@
                             + " means Shell took too long to respond to a request. WM State may be"
                             + " incorrect now, please file a bug");
                     applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
+                    if (wctApplied != null) {
+                        wctApplied.meet();
+                    }
                     return transition.getToken();
                 }
                 transition.mLogger.mStartWCT = wct;
@@ -344,11 +360,17 @@
                         synchronized (mService.mGlobalLock) {
                             transition.start();
                             applyTransaction(wct, -1 /* syncId */, transition, caller);
+                            if (wctApplied != null) {
+                                wctApplied.meet();
+                            }
                         }
                     });
                 } else {
                     transition.start();
                     applyTransaction(wct, -1 /* syncId */, transition, caller);
+                    if (wctApplied != null) {
+                        wctApplied.meet();
+                    }
                 }
                 // Since the transition is already provided, it means WMCore is determining the
                 // "readiness lifecycle" outside the provided transaction, so don't set ready here.
@@ -1159,9 +1181,13 @@
             }
             case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
                 final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
-                if (container == null || container.asDisplayArea() == null
-                        || !container.isAttached()) {
-                    Slog.e(TAG, "Attempt to operate on unknown or detached display area: "
+                if (container == null || !container.isAttached()) {
+                    Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+                            + container);
+                    break;
+                }
+                if (container.asTask() == null && container.asDisplayArea() == null) {
+                    Slog.e(TAG, "Cannot set always-on-top on non-task or non-display area: "
                             + container);
                     break;
                 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index bded9b4..809a0e8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -919,6 +919,7 @@
             assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants));
             mSetFlagsRule.disableFlags(FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER);
             when(mService.isBatteryCharging()).thenReturn(false);
+            when(mService.isBatteryNotLow()).thenReturn(false);
 
             when(mNetPolicyManagerInternal.getSubscriptionOpportunisticQuota(
                     any(), eq(NetworkPolicyManagerInternal.QUOTA_TYPE_JOBS)))
@@ -938,6 +939,12 @@
             assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants));
 
             when(mService.isBatteryCharging()).thenReturn(true);
+            assertFalse(controller.isSatisfied(latePrefetch, net, caps, mConstants));
+            // Only relax restrictions when we at least know the estimated download bytes.
+            assertFalse(controller.isSatisfied(latePrefetchUnknownDown, net, caps, mConstants));
+            assertFalse(controller.isSatisfied(latePrefetchUnknownUp, net, caps, mConstants));
+
+            when(mService.isBatteryNotLow()).thenReturn(true);
             assertTrue(controller.isSatisfied(latePrefetch, net, caps, mConstants));
             // Only relax restrictions when we at least know the estimated download bytes.
             assertFalse(controller.isSatisfied(latePrefetchUnknownDown, net, caps, mConstants));
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 7a6ac4e..e7f1d16e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -100,6 +101,8 @@
     @Mock
     private LauncherApps mLauncherApps;
     @Mock
+    private ActivityManager mActivityManager;
+    @Mock
     private PackageManager mPackageManager;
     @Mock
     private PackageInstallerService mInstallerService;
@@ -159,6 +162,10 @@
         when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
         when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
                 mLauncherActivityInfos);
+
+        when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
+        when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100);
+
         doReturn(mComputer).when(mPackageManagerService).snapshotComputer();
         when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn(
                 Binder.getCallingUid());
@@ -172,7 +179,7 @@
 
         mArchiveManager = spy(new PackageArchiver(mContext, mPackageManagerService));
         doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
-                any(LauncherActivityInfo.class), eq(mUserId), anyInt());
+                any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
         doReturn(mIcon).when(mArchiveManager).decodeIcon(
                 any(ArchiveState.ArchiveActivityInfo.class));
     }
@@ -277,7 +284,7 @@
     public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException {
         IOException e = new IOException("IO");
         doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
-                any(LauncherActivityInfo.class), eq(mUserId), anyInt());
+                any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
 
         mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
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 24a628e..d26d671 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/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index fe2ac17..f5d50d1 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -304,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/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 9fca513..d09aa89 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -25,8 +25,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
@@ -46,6 +44,7 @@
 import android.os.IHintSession;
 import android.os.PerformanceHintManager;
 import android.os.Process;
+import android.util.Log;
 
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -58,7 +57,14 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.LockSupport;
 
 /**
  * Tests for {@link com.android.server.power.hint.HintManagerService}.
@@ -67,8 +73,11 @@
  *  atest FrameworksServicesTests:HintManagerServiceTest
  */
 public class HintManagerServiceTest {
+    private static final String TAG = "HintManagerServiceTest";
+
     private static final long DEFAULT_HINT_PREFERRED_RATE = 16666666L;
     private static final long DEFAULT_TARGET_DURATION = 16666666L;
+    private static final long CONCURRENCY_TEST_DURATION_SEC = 10;
     private static final int UID = Process.myUid();
     private static final int TID = Process.myPid();
     private static final int TGID = Process.getThreadGroupLeader(TID);
@@ -103,6 +112,55 @@
         LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
     }
 
+    static class NativeWrapperFake extends NativeWrapper {
+        @Override
+        public void halInit() {
+        }
+
+        @Override
+        public long halGetHintSessionPreferredRate() {
+            return 1;
+        }
+
+        @Override
+        public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) {
+            return 1;
+        }
+
+        @Override
+        public void halPauseHintSession(long halPtr) {
+        }
+
+        @Override
+        public void halResumeHintSession(long halPtr) {
+        }
+
+        @Override
+        public void halCloseHintSession(long halPtr) {
+        }
+
+        @Override
+        public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) {
+        }
+
+        @Override
+        public void halReportActualWorkDuration(
+                long halPtr, long[] actualDurationNanos, long[] timeStampNanos) {
+        }
+
+        @Override
+        public void halSendHint(long halPtr, int hint) {
+        }
+
+        @Override
+        public void halSetThreads(long halPtr, int[] tids) {
+        }
+
+        @Override
+        public void halSetMode(long halPtr, int mode, boolean enabled) {
+        }
+    }
+
     private HintManagerService createService() {
         mService = new HintManagerService(mContext, new Injector() {
             NativeWrapper createNativeWrapper() {
@@ -112,6 +170,15 @@
         return mService;
     }
 
+    private HintManagerService createServiceWithFakeWrapper() {
+        mService = new HintManagerService(mContext, new Injector() {
+            NativeWrapper createNativeWrapper() {
+                return new NativeWrapperFake();
+            }
+        });
+        return mService;
+    }
+
     @Test
     public void testInitializeService() {
         HintManagerService service = createService();
@@ -166,8 +233,7 @@
             latch.countDown();
         });
         latch.await();
-
-        assumeFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
         verify(mNativeWrapperMock, times(1)).halPauseHintSession(anyLong());
 
         // Set session to foreground and calling updateHintAllowed() would invoke resume();
@@ -181,7 +247,7 @@
         });
         latch2.await();
 
-        assumeTrue(a.updateHintAllowed());
+        assertTrue(service.mUidObserver.isUidForeground(a.mUid));
         verify(mNativeWrapperMock, times(1)).halResumeHintSession(anyLong());
     }
 
@@ -254,7 +320,7 @@
         });
         latch.await();
 
-        assumeFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
         a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE);
         verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
     }
@@ -280,7 +346,7 @@
         service.mUidObserver.onUidStateChanged(
                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         FgThread.getHandler().runWithScissors(() -> { }, 500);
-        assertFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
         a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
         verify(mNativeWrapperMock, never()).halSendHint(anyLong(), anyInt());
     }
@@ -303,7 +369,7 @@
         });
         latch.await();
 
-        assertFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
     }
 
     @Test
@@ -316,7 +382,7 @@
 
         service.mUidObserver.onUidStateChanged(
                 a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
-        assertTrue(a.updateHintAllowed());
+        assertTrue(service.mUidObserver.isUidForeground(a.mUid));
     }
 
     @Test
@@ -342,7 +408,7 @@
         service.mUidObserver.onUidStateChanged(
                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         FgThread.getHandler().runWithScissors(() -> { }, 500);
-        assertFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
         a.setThreads(SESSION_TIDS_A);
         verify(mNativeWrapperMock, never()).halSetThreads(anyLong(), any());
     }
@@ -372,9 +438,159 @@
         service.mUidObserver.onUidStateChanged(
                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
         FgThread.getHandler().runWithScissors(() -> { }, 500);
-        assertFalse(a.updateHintAllowed());
+        assertFalse(service.mUidObserver.isUidForeground(a.mUid));
         a.setMode(0, true);
         verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean());
     }
 
+    // This test checks that concurrent operations from different threads on IHintService,
+    // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also
+    // check the output of threads' reportActualDuration performance to detect lock starvation
+    // but the result is not stable, so it's better checked manually.
+    @Test
+    public void testConcurrency() throws Exception {
+        HintManagerService service = createServiceWithFakeWrapper();
+        // initialize session threads to run in parallel
+        final int sessionCount = 10;
+        // the signal that the main thread will send to session threads to check for run or exit
+        AtomicReference<Boolean> shouldRun = new AtomicReference<>(true);
+        // the signal for main test thread to wait for session threads and notifier thread to
+        // finish and exit
+        CountDownLatch latch = new CountDownLatch(sessionCount + 1);
+        // list of exceptions with one per session thread or notifier thread
+        List<AtomicReference<Exception>> execs = new ArrayList<>(sessionCount + 1);
+        List<Thread> threads = new ArrayList<>(sessionCount + 1);
+        for (int i = 0; i < sessionCount; i++) {
+            final AtomicReference<Exception> exec = new AtomicReference<>();
+            execs.add(exec);
+            int j = i;
+            Thread app = new Thread(() -> {
+                try {
+                    while (shouldRun.get()) {
+                        runAppHintSession(service, j, shouldRun);
+                    }
+                } catch (Exception e) {
+                    exec.set(e);
+                } finally {
+                    latch.countDown();
+                }
+            });
+            threads.add(app);
+        }
+
+        // initialize a UID state notifier thread to run in parallel
+        final AtomicReference<Exception> notifierExec = new AtomicReference<>();
+        execs.add(notifierExec);
+        Thread notifier = new Thread(() -> {
+            try {
+                long min = Long.MAX_VALUE;
+                long max = Long.MIN_VALUE;
+                long sum = 0;
+                int count = 0;
+                while (shouldRun.get()) {
+                    long start = System.nanoTime();
+                    service.mUidObserver.onUidStateChanged(UID,
+                            ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
+                    long elapsed = System.nanoTime() - start;
+                    sum += elapsed;
+                    count++;
+                    min = Math.min(min, elapsed);
+                    max = Math.max(max, elapsed);
+                    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
+                    service.mUidObserver.onUidStateChanged(UID,
+                            ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+                    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
+                }
+                Log.d(TAG, "notifier thread min " + min + " max " + max + " avg " + sum / count);
+                service.mUidObserver.onUidGone(UID, true);
+            } catch (Exception e) {
+                notifierExec.set(e);
+            } finally {
+                latch.countDown();
+            }
+        });
+        threads.add(notifier);
+
+        // start all the threads
+        for (Thread thread : threads) {
+            thread.start();
+        }
+        // keep the test running for a few seconds
+        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(CONCURRENCY_TEST_DURATION_SEC));
+        // send signal to stop all threads
+        shouldRun.set(false);
+        // wait for all threads to exit
+        latch.await();
+        // check if any thread throws exception
+        for (AtomicReference<Exception> exec : execs) {
+            if (exec.get() != null) {
+                throw exec.get();
+            }
+        }
+    }
+
+    private void runAppHintSession(HintManagerService service, int logId,
+            AtomicReference<Boolean> shouldRun) throws Exception {
+        IBinder token = new Binder();
+        AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+                .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+        // we will start some threads and get their valid TIDs to update
+        int threadCount = 3;
+        // the list of TIDs
+        int[] tids = new int[threadCount];
+        // atomic index for each thread to set its TID in the list
+        AtomicInteger k = new AtomicInteger(0);
+        // signal for the session main thread to wait for child threads to finish updating TIDs
+        CountDownLatch latch = new CountDownLatch(threadCount);
+        // signal for the session main thread to notify child threads to exit
+        CountDownLatch stopLatch = new CountDownLatch(1);
+        for (int j = 0; j < threadCount; j++) {
+            Thread thread = new Thread(() -> {
+                try {
+                    tids[k.getAndIncrement()] = android.os.Process.myTid();
+                    latch.countDown();
+                    stopLatch.await();
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+            thread.start();
+        }
+        latch.await();
+        a.setThreads(tids);
+        // we don't need the threads to exist after update
+        stopLatch.countDown();
+        a.updateTargetWorkDuration(5);
+        // measure the time it takes in HintManagerService to run reportActualDuration
+        long min = Long.MAX_VALUE;
+        long max = Long.MIN_VALUE;
+        long sum = 0;
+        int count = 0;
+        List<Long> values = new ArrayList<>();
+        long testStart = System.nanoTime();
+        // run report actual for 4-second per cycle
+        while (shouldRun.get() && System.nanoTime() - testStart < TimeUnit.SECONDS.toNanos(
+                Math.min(4, CONCURRENCY_TEST_DURATION_SEC))) {
+            long start = System.nanoTime();
+            a.reportActualWorkDuration(new long[]{5}, new long[]{start});
+            long elapsed = System.nanoTime() - start;
+            values.add(elapsed);
+            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(5));
+            sum += elapsed;
+            count++;
+            min = Math.min(min, elapsed);
+            max = Math.max(max, elapsed);
+        }
+        Collections.sort(values);
+        if (!values.isEmpty()) {
+            Log.d(TAG, "app thread " + logId + " min " + min + " max " + max
+                    + " avg " + sum / count + " count " + count
+                    + " 80th " + values.get((int) (values.size() * 0.8))
+                    + " 90th " + values.get((int) (values.size() * 0.9))
+                    + " 95th " + values.get((int) (values.size() * 0.95)));
+        } else {
+            Log.w(TAG, "No reportActualWorkDuration executed");
+        }
+        a.close();
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 51b9c17..47f15b8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -18,6 +18,7 @@
 import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT;
 import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
 
+import static com.google.common.truth.Truth.assertThat;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -486,6 +487,29 @@
     }
 
     @Test
+    public void testRepostAll() throws Exception {
+        final int profileId = 11;
+        final int otherUserId = 2;
+        IntArray userIds = new IntArray();
+        userIds.add(UserHandle.USER_SYSTEM);
+        userIds.add(profileId);
+        NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+        NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
+        NotificationRecord r3 = getNotificationRecord("pkg", 3, "three", UserHandle.of(profileId));
+        NotificationRecord r4 = getNotificationRecord("pkg", 4, "four", UserHandle.of(otherUserId));
+        mSnoozeHelper.snooze(r,  1000);
+        mSnoozeHelper.snooze(r2, 1000);
+        mSnoozeHelper.snooze(r3, 1000);
+        mSnoozeHelper.snooze(r4, 1000);
+
+        mSnoozeHelper.repostAll(userIds);
+
+        verify(mCallback, times(3)).repost(anyInt(), any(), anyBoolean());
+        // All notifications were reposted, except the one for otherUserId
+        assertThat(mSnoozeHelper.getSnoozed()).containsExactly(r4);
+    }
+
+    @Test
     public void testGetSnoozedBy() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index c8546c6..4773023 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -2475,6 +2475,46 @@
         verify(session).close();
     }
 
+    @Test
+    public void testReadyTrackerBasics() {
+        final TransitionController controller = new TestTransitionController(
+                mock(ActivityTaskManagerService.class));
+        controller.setFullReadyTrackingForTest(true);
+        Transition transit = createTestTransition(TRANSIT_OPEN, controller);
+        // Not ready if nothing has happened yet
+        assertFalse(transit.mReadyTracker.isReady());
+
+        Transition.ReadyCondition condition1 = new Transition.ReadyCondition("c1");
+        transit.mReadyTracker.add(condition1);
+        assertFalse(transit.mReadyTracker.isReady());
+
+        Transition.ReadyCondition condition2 = new Transition.ReadyCondition("c2");
+        transit.mReadyTracker.add(condition2);
+        assertFalse(transit.mReadyTracker.isReady());
+
+        condition2.meet();
+        assertFalse(transit.mReadyTracker.isReady());
+
+        condition1.meet();
+        assertTrue(transit.mReadyTracker.isReady());
+    }
+
+    @Test
+    public void testReadyTrackerAlternate() {
+        final TransitionController controller = new TestTransitionController(
+                mock(ActivityTaskManagerService.class));
+        controller.setFullReadyTrackingForTest(true);
+        Transition transit = createTestTransition(TRANSIT_OPEN, controller);
+
+        Transition.ReadyCondition condition1 = new Transition.ReadyCondition("c1");
+        transit.mReadyTracker.add(condition1);
+        assertFalse(transit.mReadyTracker.isReady());
+
+        condition1.meetAlternate("reason1");
+        assertTrue(transit.mReadyTracker.isReady());
+        assertEquals("reason1", condition1.mAlternate);
+    }
+
     private static void makeTaskOrganized(Task... tasks) {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         for (Task t : tasks) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 0b77fd8..cd3fef6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1646,6 +1646,28 @@
         verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
     }
 
+    @Test
+    public void testSetAlwaysOnTop() {
+        final Task rootTask = new TaskBuilder(mSupervisor)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+        testSetAlwaysOnTop(rootTask);
+
+        final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea();
+        displayArea.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        testSetAlwaysOnTop(displayArea);
+    }
+
+    private void testSetAlwaysOnTop(WindowContainer wc) {
+        final WindowContainerTransaction t = new WindowContainerTransaction();
+        t.setAlwaysOnTop(wc.mRemoteToken.toWindowContainerToken(), true);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+        assertTrue(wc.isAlwaysOnTop());
+
+        t.setAlwaysOnTop(wc.mRemoteToken.toWindowContainerToken(), false);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+        assertFalse(wc.isAlwaysOnTop());
+    }
+
     private ActivityRecord createActivityRecordAndDispatchPendingEvents(Task task) {
         final ActivityRecord record = createActivityRecord(task);
         // Flush EVENT_APPEARED.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0b70b40e..042b2a3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8806,7 +8806,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";
@@ -8874,7 +8876,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/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 6b47db1..e2cd4f8 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -469,6 +469,9 @@
      * The satellite service should report the NTN signal strength via
      * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes.
      *
+     * Note: This API can be invoked multiple times. If the modem is already in the expected
+     * state from a previous request, subsequent invocations may be ignored.
+     *
      * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid result codes returned:
@@ -485,6 +488,9 @@
      * be called when device is screen off to save power by not letting signal strength updates to
      * wake up application processor.
      *
+     * Note: This API can be invoked multiple times. If the modem is already in the expected
+     * state from a previous request, subsequent invocations may be ignored.
+     *
      * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid result codes returned: